2016-03-02 27 views
6

Based of this answer, chcę stworzyć one line tree jako część innej klasy jak tak:One linię realizacja drzewo

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

będę potrzebował, aby umożliwić użytkownikom powiedział klasa dodać elementy ścieżki do drzewa i uruchomić niektóre wywołanie zwrotne rozpoczynające się od najniższego poziomu drzewa. Mój naiwny realizacja podnosi błąd, gdy biegnę 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) 

Ten kod działa IFF drzewo jest zdefiniowany jako

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

    self._tree = tree() 

Dlaczego moje naiwne podejście nie działa z wyrażenia lambda?


⚠ The Zen of Python stwierdza, że ​​

Proste jest lepsze niż skomplikowane.

Jednokresowa lambda tworzy kompleks kodu, w którym znajduje się simpler implementation. Dlatego jednolinijkowa lambda nie powinna być używana w kodzie produkcyjnym. Pozostawiam jednak to pytanie tutaj dla zainteresowania akademickiego.

+0

Metoda przekazana do 'defaultdict' musi utworzyć nową instancję, a nie odwołanie do istniejącego obiektu. W twoim przypadku 'drzewo' jest metodą, która zwraca nowe' defaultdict (drzewo) ', ale twoja jedna linijka to lambda, która zwraca oryginalną instancję' self._tree' 'defaultdict (lambda: etc.)' – PaulMcG

+0

@ PaulMcGuire: Więc dlaczego 'd = collections.defaultdict (lambda: d); d ['foo'] ['bar'] 'wydaje się działać dobrze pomimo brzydkiego podczas drukowania? – Sardathrion

+0

Odpowiedź Kevina i komentarz Tadhga McDonald-Jensena ilustrują niewłaściwą rekurencję w jednoliniowym podejściu lambda. – PaulMcG

Odpowiedz

5

Jednokresowy wzór defaultdict w pierwszym połączonym pytaniu nie pasuje do mnie. Produkuje niezwykłe autoreferencyjna pętle:

>>> 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... 

Implementacja lambda jedna linia funkcji w drugim linku będzie wyglądał bardziej jak:

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


EDIT: wygląda jak ty można to zrobić w jednym zestawieniu z:

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

... Ale wymagające umiejętności liczenia lambda na poziomie college'u po to, by zmniejszyć swój skrypt o jedno zdanie, wydaje się nierozsądną okazją. Rozważ bardziej zrozumiałe podejście.

+0

można to łatwo zweryfikować za pomocą 'assert d is d ['b']', zanim wartość zostanie ustawiona na klucz "b". po prostu odnosi się do siebie. –

+0

Aby zignorować komentarze z pytaniem "OK, ale czy można to zrobić w jednym wierszu bez średników?" Myślę, że jest to teoretycznie możliwe, ale wymaga to dość skomplikowanych zastosowań lambda. Mogą być wymagane kombinatory o ustalonych punktach. Nadal się z tym bawię. – Kevin

+1

@Kevin: Dziękujemy! Oczywiście nie powinno się [przypisywać lambdy w pierwszej kolejności] (http://legacy.python.org/dev/peps/pep-0008/#programming-recommendations). – Sardathrion

2

nawet z kodem z that answer jest to mającego dokładnie ten sam problem:

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

każdy sub dict jest tworzenie tylko odniesienie do siebie zamiast nowego słownika.

Aby to zadziałało poprawnie, lambda musi utworzyć nowy obiekt o wartości defaultdict (wyrażenie lambda) jako pierwszy argument. Jednak jedyne odniesienie do lambda jest utrzymywana jako self._tree.default_factory więc jedna wkładka musiałaby wyglądać następująco:

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

To jest tak nieodłącznie mylące, że nie da się przecenić ile Polecam przeciwko robi w jednej linii.

+1

100% zgadza się z ostatecznym oświadczeniem, pytanie zostało zredagowane, aby było jasne dla przyszłych czytelników. – Sardathrion