2012-04-09 15 views
7

chciałem mieć listę lambdas które działają jako rodzaj pamięci podręcznej do niektórych ciężkich obliczeń i zauważyłem to:lambdas lista wewnątrz Ułatwienia

>>> [j() for j in [lambda:i for i in range(10)]] 
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

Chociaż

>>> list([lambda:i for i in range(10)]) 
[<function <lambda> at 0xb6f9d1ec>, <function <lambda> at 0xb6f9d22c>, <function <lambda> at 0xb6f9d26c>, <function <lambda> at 0xb6f9d2ac>, <function <lambda> at 0xb6f9d2ec>, <function <lambda> at 0xb6f9d32c>, <function <lambda> at 0xb6f9d36c>, <function <lambda> at 0xb6f9d3ac>, <function <lambda> at 0xb6f9d3ec>, <function <lambda> at 0xb6f9d42c>] 

ten sposób, że lambdas są unikalne funkcje, ale w jakiś sposób wszystkie mają tę samą wartość indeksu.

Czy to błąd lub funkcja? Jak mogę uniknąć tego problemu? To nie jest ograniczone do listowych ...

>>> funcs = [] 
... for i in range(10): 
...  funcs.append(lambda:i) 
... [j() for j in funcs] 
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

Odpowiedz

13

Parametr lambda zwraca wartość i w momencie jej wywołania. Ponieważ wywołujesz lambda po zakończeniu pętli, wartość i zawsze będzie wynosić 9.

Można utworzyć lokalną zmienną i w lambda do przechowywania wartości w momencie lambda zostało zdefiniowane:

>>> [j() for j in [lambda i=i:i for i in range(10)]] 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Innym rozwiązaniem jest stworzenie funkcji, która zwraca lambda:

def create_lambda(i): 
    return lambda:i 
>>> [j() for j in [create_lambda(i) for i in range(10)]] 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Działa to, ponieważ dla każdego wywołania create_lambda utworzone jest inne zamknięcie (o innej wartości: i).

+0

Pierwsze podejście nie działa tak dobrze, gdy chcesz, aby 'lambda' obsługiwała' * args'. Drugie podejście będzie tam działać, ale jest ... więcej pracy :) –

+2

Szkoda, nie mogę dać zielonego znaku dla obu odpowiedzi. Wybrałem ten, ponieważ podał poprawny kod do wycinania i wklejania, tak jakbym miał pięć lat i podoba mi się to podejście do odpowiedzi na SO. – ubershmekel

+0

Dla tych z nas mniej dobrych w Pythonie, czy jest szansa, że ​​użyjesz innej zmiennej niż 'i' dla jednej z instancji w pierwszym podejściu? – Chowlett

0

Nie jestem pewien, czy jest to błąd lub funkcja, ale co się dzieje, że nie ocenia lambda:i I przed utworzeniem funkcji lambda. Tak więc jest to dosłownie funkcja, która ocenia cokolwiek aktualną wartość i. Oto kolejny przykład tego, jak to się dzieje.

>>> i=5 
>>> x=lambda:i 
>>> x() 
5 
>>> i=6 
>>> x() 
6 

Tak, oczywiście, to co się dzieje to samo, poza tym, że ma zamiar 9 w swoich przykładach jak to jest przydzielony w całym zakresie od 0 do 9, w tej kolejności.

Nie sądzę, że istnieje naprawdę dobry sposób na uniknięcie tego. Funkcje Lambda w Pythonie są dość ograniczone. W rzeczywistości nie jest to język funkcjonalny.

7

To, co tu widzisz, to efekt closures. Lambda przechwytuje stan z programu, który będzie używany później. Tak więc, podczas gdy każda lambda jest unikalnym obiektem, stan niekoniecznie jest unikalny.

W rzeczywistości "gotchya" oznacza, że ​​zmienna i została przechwycona, a nie wartość i reprezentująca w tym momencie. Możemy zilustrować to ze znacznie łatwiejszy przykład:

>>> y = 3 
>>> f = lambda: y 
>>> f() 
3 
>>> y = 4 
>>> f() 
4 

lambda trzyma się odniesieniem do zmiennej, zmienna i ocenia, że ​​podczas wykonywania lambda.

Aby obejść ten problem, można przypisać do zmiennej lokalnej w lambda:

>>> f = lambda y=y:y 
>>> f() 
4 
>>> y = 6 
>>> f() 
4 

Wreszcie, w przypadku pętli, zmienna pętla jest tylko "ogłoszony raz. Dlatego wszelkie odwołania do zmiennej pętli w pętli będą kontynuowane po następnej iteracji. Obejmuje to zmienną w zrozumieniu listy.

+0

Cóż, każda funkcja lambda jest zamknięciem. Nie sądzę, żeby plakat był zdezorientowany tym, co to było zamknięcie. Nieoczekiwana część polega na tym, że w tym przypadku używa ona zmiennej referencyjnej, a nie wartości. Większość języków, w których używane są zamknięcia, zastąpiłoby tę wartość w tych okolicznościach, gdyby nie użyto typu referencyjnego. –

+4

@KeithIrwin, większość języków, które pozwalają na zamknięcie, cierpi z powodu tej właśnie "gotchya". JavaScript i C# to dwa godne uwagi przykłady. Poza tym OP nigdy nie wspomniał o zamknięciu słowa, więc pomyślałem, że powiążę z materiałem, który je wyjaśni, jeśli nie będzie tego świadomy. –

+0

Pojawia się tylko w językach, które nie jednoznacznie rozróżniają wartości i odniesienia. Na przykład pojawia się w rodzinie języków funkcyjnych Lisp, ale nie w językach o silniejszym typie, takich jak rodzina ML lub Haskell lub Scala. Gdyby pracował w języku takim jak F #, Ocaml, Haskell lub Scala, rozsądnie oczekiwałby zachowania dokładnie przeciwnego. Zakładam, że ma co najmniej pewne doświadczenie z językami funkcjonalnymi, ponieważ oznaczył stanowisko jako o programowaniu funkcjonalnym. –

1

Problem polega na tym, że nie przechwytujesz wartości i w każdej iteracji spisu, a za każdym razem przechwytujesz zmienną.

Problem polega na tym, że zamknięcie przechwytuje zmienne przez odniesienie. W tym przypadku przechwytujesz zmienną, której wartość zmienia się w czasie (tak jak w przypadku wszystkich zmiennych pętli), więc ma inną wartość po uruchomieniu niż podczas jej tworzenia.

0

Nie jestem pewien, co próbujesz zrobić z funkcją lambda. To nie jest argumentem ...

Myślę, że Python tworzy generator podobny do (i for i in range(10)), a następnie iteruje to. Po zliczeniu 10 razy, ostateczna wartość i wynosi 9, a następnie to właśnie zwraca funkcja lambda.

Czy chcesz zrobić jakiś przedmiot, który dałby liczbę od [0,9]? Ponieważ jeśli chcesz to zrobić, po prostu użyj xrange(10), która zwraca iterator, który daje liczby [0,9].

def f(x): 
    return x 

lst = [f(x) for x in xrange(10)] 
print(lst == range(10)) # prints True 

Uwaga: Myślę, że jest to bardzo zły pomysł, aby spróbować wymienić funkcję j(). Python używa j do wskazania części urojonej liczby zespolonej i myślę, że funkcja o nazwie j może zmylić analizator składni. Dostałem kilka dziwnych komunikatów o błędach próbujących uruchomić twój kod.