2009-01-17 26 views
24

Chcę utworzyć listę obiektów lambda z listy stałych w Pythonie; na przykład:Jak utworzyć listę lambd do Pythona (w pętli zrozumienie/pętla)?

listOfNumbers = [1,2,3,4,5] 
square = lambda x: x * x 
listOfLambdas = [lambda: square(i) for i in listOfNumbers] 

To stworzy listę obiektów lambda, jednak gdy uruchamiam nich

for f in listOfLambdas: 
    print f(), 

Spodziewam się, że będzie drukować

1 4 9 16 25 

Zamiast tego, wydruki:

25 25 25 25 25 

Wygląda na to, że wszystkie lambdy otrzymały niewłaściwy parametr. Czy zrobiłem coś złego i czy istnieje sposób, aby to naprawić? Jestem w Pythonie 2.4.

EDIT: nieco bardziej próbować rzeczy i takie wymyślił to:

listOfLambdas = [] 
for num in listOfNumbers: 
    action = lambda: square(num) 
    listOfLambdas.append(action) 
    print action() 

Drukuje oczekiwane kwadraty od 1 do 25, ale następnie przy użyciu wcześniejsze oświadczenie drukowania:

for f in listOfLambdas: 
    print f(), 

nadal daje mi wszystkie 25 s. W jaki sposób istniejące obiekty lambda zmieniają się między tymi dwoma wywołaniami drukowania?

Powiązane pytanie: Why results of map() and list comprehension are different?

Odpowiedz

17

Zgaduję, że lambda tworzysz na liście zrozumienia jest zobowiązany do zmiennej I, który ostatecznie kończy się na 5. Tak więc, jeśli oceniać po fakcie lambdy , wszyscy są zobowiązani do 5 i kończą na obliczaniu 25. To samo dzieje się z num w twoim drugim przykładzie. Kiedy oceniasz lambdę w pętli, jej liczba nie zmieniła się, więc otrzymujesz właściwą wartość. Po pętli numer to 5 ...

Nie jestem do końca pewien, co zamierzasz, więc nie jestem pewien, jak zaproponować rozwiązanie. Co powiesz na to?

def square(x): return lambda : x*x 
listOfLambdas = [square(i) for i in [1,2,3,4,5]] 
for f in listOfLambdas: print f() 

To daje mi oczekiwany wynik:

1 
4 
9 
16 
25 

Innym sposobem myślenia jest to, że lambda „przechwytuje” jego leksykalny środowisko w miejscu, gdzie jest on utworzony. Tak więc, jeśli dasz mu num, to tak naprawdę nie rozwiąże tej wartości, dopóki nie zostanie wywołana. Jest to zarówno mylące, jak i potężne.

0

czasem okaże się, że zdefiniowanie rzeczywistych klas obiektów funkcyjnych sprawia, że ​​łatwiej zrozumieć, co się dzieje:

>>> class square(object): 
... def __init__(self, val): 
...  self.val = val 
... def __call__(self): 
...  return self.val * self.val 
... 
>>> l = [1,2,3,4,5] 
>>> funcs = [square(i) for i in l] 
>>> for f in funcs: 
... print f() 
... 
1 
4 
9 
16 
25 
>>> 

To prawda, jest to trochę bardziej gadatliwy niż przy użyciu lambdy lub zamknięć, ale uważam, że to łatwiejsze do zrozumienia kiedy próbuję robić nieoczywiste rzeczy za pomocą funkcji.

18

Masz:

listOfLambdas = [lambda: i*i for i in range(6)] 

for f in listOfLambdas: 
    print f() 

wyjściowa:

25 
25 
25 
25 
25 
25 

Trzeba Zmiękczanie! Oprócz tego, że jest pyszny, użyj tej domyślnej wartości "hack".

listOfLambdas = [lambda i=i: i*i for i in range(6)] 

for f in listOfLambdas: 
    print f() 

wyjściowa:

0 
1 
4 
9 
16 
25 

Zanotuj i=i. Tam właśnie dzieje się magia.

+2

Cool. Czy to "hack" udokumentowane w dowolnym miejscu? Czy istnieje lepszy sposób na curry? Proszę również, nie wspominać o obrotowym łóżku. –

+1

Nigdy nie widziałem tego wyraźnie wymienionego w żadnej dokumentacji. Wykorzystuje to fakt, że wartości domyślne dla parametrów funkcji są przypisywane w momencie tworzenia funkcji, co jest udokumentowane. – recursive

+1

Ten przykład jest prawie taki sam jak [Dlaczego lambda zdefiniowana w pętli z różnymi wartościami zwraca ten sam wynik?] (Https://docs.python.org/3.4/faq/programming.html#why-do-lambdas- zdefiniowane w pętli-z-różnymi wartościami-all-return-the-same-result) z dokumentów. – abarnert

2
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers] 

Albo

listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers) 
3

Jeżeli sprawozdanie funkcyjne są wykonywane są one zobowiązane do ich zakresu (leksykalnie) osłaniającego.

W Twoim ujęciu Lambda jest powiązana z zasięgiem globalnym, ponieważ pakiety for nie są wykonywane w Pythonie jako jednostka o niezależnym zakresie. Na końcu pętli for, num jest związany w otaczającym zakresie. Demo:

for num in range(1, 6): 
    pass 
assert num == 5 # num is now bound in the enclosing scope 

Więc kiedy wiążą identyfikatory w pętli for jesteś rzeczywiście manipulowania zakresu obejmującego.

for num in range(1, 6): 
    spam = 12 
assert num == 5 # num is now bound in the enclosing scope 
assert spam == 12 # spam is also bound in the enclosing scope 

samą ofertę dla listowych:

[num for num in range(1, 6)] 
assert num == 5 

niewiarygodny, wiem. Anywho, dzięki naszej nowo poznanej wiedzy, możemy określić, że tworzone lambdy odnoszą się do (pojedynczego) identyfikatora związanego w otaczającym oscyloskopie. Które powinny uczynić to więcej sensu:

functions = [] 
for number in range(1, 6): 
    def fun(): 
     return number 
    functions.append(fun) 
assert all(fun() == 5 for fun in functions) 
assert all(fun() is number for fun in functions) 

A oto najfajniejsza część, która pokazuje, że nawet więcej:

# Same as above -- commented out for emphasis. 
#functions = [] 
#for number in range(1, 6): 
# def fun(): 
#  return number 
# functions.append(fun) 
#assert all(fun() == 5 for fun in functions) 
#assert all(fun() is number for fun in functions) 
number = 6 # Rebind 6 in the scope and see how it affects the results. 
assert all(fun() == 6 for fun in functions) 

więc rozwiązanie to, oczywiście, jest, aby nowy zakres otaczającą dla każde number, które chcesz powiązać. W Pythonie możesz tworzyć nowe obramowania z modułami, klasami i funkcjami. Często używa się funkcji tylko do utworzenia nowego zakresu obejmującego inną funkcję.

W języku Python zamknięcie jest funkcją, która powoduje, że zwraca inną funkcję. Coś jak konstruktor funkcji. Sprawdź get_fun w poniższym przykładzie:

def get_fun(value): 
    """:return: A function that returns :param:`value`.""" 
    def fun(): # Bound to get_fun's scope 
     return value 
    return fun 

functions = [] 
for number in range(1, 6): 
    functions.append(get_fun(number)) 
assert [fun() for fun in functions] == range(1, 6) 

Od get_fun jest funkcją, to dostaje się mieć własną przestrzeń wewnętrzną. Za każdym razem, gdy wywołujesz get_fun z wartością, tworzona jest mała tabela, aby śledzić powiązania w niej; tzn. mówi: "W tym zakresie identyfikator value wskazuje na rzecz, która została przekazana." Zasięg ten kończy się po zakończeniu wykonywania funkcji, chyba że istnieje powód, żeby się kręcić.

Jeśli zwracasz funkcję z zakresu, jest to dobry powód, dla którego część tabeli zakresu może się kręcić - funkcja, którą powracasz, może odwoływać się do rzeczy z tej tabeli zakresu, gdy ją wywołasz. później. Z tego powodu, gdy fun jest tworzony w ramach get_fun, Python podaje fun około tabelę zakresu, która fun jest przydatna, gdy jest potrzebna.

Możesz przeczytać więcej o szczegółach i terminologii technicznej (która nieco złagodniałem) w Python docs on the execution model. Możesz także spojrzeć na części otaczającego zakresu, do których odnosi się funkcja z print fun.__closure__. W powyższym przykładzie widzimy odniesienie do value, co zdarza się być int:

# Same as before, commented out for emphasis. 
#functions = [] 
#for number in range(1, 6): 
# functions.append(get_fun(number)) 
#assert [fun() for fun in functions] == range(1, 6) 
print functions[0].__closure__ 
# Produces: (<cell at 0x8dc30: int object at 0x1004188>,) 
1

spróbuj użyć() zamiast []:

listOfLambdas = (lambda: square(i) for i in listOfNumbers) 

I dostaniesz:

1 
4 
9 
16 
25 
0

można również zrobić:

>>> def squares(): 
...  for i in [1,2,3,4,5]: 
...   yield lambda:i*i 
... 
>>> print [square() for square in squares()] 
[1, 4, 9, 16, 25] 
0

Jako dodatkowy komentarz, chciałbym nakreślić możliwość generowania list funkcji lambda z macierzy sympy (nie wiem, czy to najlepszy sposób na zrobienie tego, ale tak to robię i uważam, że jest to wygodne) :

import sympy as sp 
sp.var('Ksi') 
# generate sympy expressions for Berstein's polynomials 
B_expr = sp.Matrix([sp.binomial(3, i) * Ksi**i * (1 - Ksi)**(3-i) for i in range(4)]) 
# lambdify them 
B = [sp.lambdify((Ksi), B_expr[i]) for i in range(4) ]