2016-03-02 32 views
6

Based of this answer, voglio creare un one line tree come parte di un'altra classe, come in tal modo:Una linea implementazione albero

self._tree = collections.defaultdict(lambda: self._tree) 

ho bisogno di consentire agli utenti di detta classe di aggiungere elementi di percorso per l'albero ed eseguire alcuni callback a partire dal livello più basso dell'albero. La mia implementazione ingenuo solleva un errore quando ho eseguito pytest:

def _add(self, tree, path): 
    for node in path: 
     tree = tree[node] 

def _run(self, tree, callback): 
    for key in tree.keys(): 
     callback(tree[key]) # !!! Recursion detected (same locals & position) 
     self._run(key) 

Questo codice funziona se e solo se l'albero è definito come

def tree(): 
     return collections.defaultdict(tree) 

    self._tree = tree() 

Perché il mio approccio ingenuo non funziona con l'espressione lambda?


⚠ Il Zen of Python afferma che

Semplice è meglio che complesso.

Il lambda a una riga rende complesso il codice in cui è presente uno simpler implementation. Pertanto la lambda a una linea non deve essere utilizzata nel codice di produzione. Tuttavia, lascerò questa domanda qui per interesse accademico.

+0

Il metodo passato a 'defaultdict' deve creare una nuova istanza, non fare riferimento a un oggetto esistente. Nel tuo caso 'tree' è un metodo che restituisce un nuovo' defaultdict (tree) ', ma il tuo one-liner è un lambda che restituisce l'istanza originale' self._tree' di 'defaultdict (lambda: ecc.)' – PaulMcG

+0

@ PaulMcGuire: Allora perché 'd = collections.defaultdict (lambda: d); d ['foo'] ['bar'] 'sembra funzionare bene nonostante sia brutto quando stampato? – Sardathrion

+0

La risposta di Kevin e il commento di Tadhg McDonald-Jensen illustrano l'impropria ricorsione nell'approccio lambda a una riga. – PaulMcG

risposta

5

Il progetto defaultdict di una riga nella prima domanda collegata non mi sembra corretto. Produce insoliti autoreferenziali loop:

>>> d = collections.defaultdict(lambda: d) 
>>> d["a"] = 23 
>>> d["b"]["c"] = 42 
>>> print d["b"]["a"] #we never created a value with these keys, so it should just return a defaultdict instance. 
23 
>>> #uh, that's not right... 

Un'implementazione lambda una riga della funzione nel secondo collegamento sarebbe più simile a:

tree = lambda: defaultdict(tree); self._tree = tree()


Edit: Sembra che si può farlo in una singola dichiarazione con:

self._tree = (lambda f: f(f))(lambda t: defaultdict(lambda: t(t))) 

... Ma richiede le abilità di calcolo lambda di livello universitario solo per ridurre il tuo copione di una frase sembra un pessimo affare. Considera un approccio più comprensibile.

+0

questo è facile da verificare con 'assert d è d ['b']' prima che un valore sia impostato sulla chiave 'b'. si riferisce solo a se stesso. –

+0

Per evitare qualsiasi commento che chiede "ok, ma puoi farlo in una riga senza punto e virgola?" Penso che sia teoricamente possibile ma richiede alcuni usi piuttosto complicati di lambda. Possono essere richiesti combinatori a virgola fissa. Ci sto ancora giocando. – Kevin

+1

@ Kevin: Grazie! Ovviamente non si dovrebbe essere [assegnando lambda in primo luogo] (http://legacy.python.org/dev/peps/pep-0008/#programming-recommendations). – Sardathrion

2

anche con il codice that answer sta avendo lo stesso problema esatto:

d = collections.defaultdict(lambda:d) 
assert d is d[1] is d[2][4] 

ogni sotto dict sta creando solo un riferimento a se stesso anziché un nuovo dizionario.

affinché funzioni correttamente, lambda deve creare un nuovo oggetto defaultdict con se stesso (l'espressione lambda) come primo argomento. Tuttavia l'unico riferimento alla lambda è mantenuto come self._tree.default_factory in modo che l'one-liner dovrebbe assomigliare a questo:

self._tree = collections.defaultdict(lambda:collections.defaultdict(self._tree.default_factory)) 

Questo è così intrinsecamente confusione che non posso sottolineare abbastanza quanto vi consiglio contro farlo in una riga.

+1

100% d'accordo con la tua dichiarazione finale, domanda modificata per renderla chiara per i futuri lettori. – Sardathrion