Bardziej preferowane: funkcje lambda czy funkcje zagnieżdżone ( def
)?
Stosowanie lambdy w stosunku do funkcji regularnej ma jedną zaletę: są tworzone w wyrażeniu.
Istnieje kilka wad:
- bez imienia (tylko
'<lambda>'
)
- bez dokumentów
- brak adnotacji
- brak skomplikowanych instrukcji
Oba są również tego samego rodzaju obiektami. Z tych powodów generalnie wolę tworzyć funkcje ze def
słowem kluczowym zamiast z lambdami.
Punkt pierwszy - są to obiekty tego samego typu
Lambda daje ten sam typ obiektu, co zwykła funkcja
>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
...
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True
Ponieważ lambdy są funkcjami, są obiektami pierwszej klasy.
Zarówno lambdy, jak i funkcje:
- może być przekazywany jako argument (tak samo jak zwykła funkcja)
- gdy są tworzone w ramach funkcji zewnętrznej, stają się zamknięciem dla lokalności tej funkcji zewnętrznej
Jednak w wyrażeniach lambdowych domyślnie brakuje niektórych rzeczy, które funkcje uzyskują za pośrednictwem pełnej składni definicji funkcji.
A Lamba za __name__
znaczy'<lambda>'
Lambdy to w końcu funkcje anonimowe, więc nie znają swojego imienia.
>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'
Zatem lambda nie mogą być wyszukiwane programowo w ich przestrzeni nazw.
To ogranicza pewne rzeczy. Na przykład foo
można wyszukać za pomocą serializowanego kodu, podczas gdy l
nie można:
>>> import pickle
>>> pickle.loads(pickle.dumps(l))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fbbc0464e18>:
attribute lookup <lambda> on __main__ failed
Możemy dobrze wyszukać foo
- ponieważ zna swoją nazwę:
>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>
Lambdy nie mają adnotacji ani napisów
Zasadniczo lambdy nie są udokumentowane. Przepiszmy, foo
aby być lepiej udokumentowanym:
def foo() -> int:
"""a nullary function, returns 0 every time"""
return 0
Teraz foo ma dokumentację:
>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:
foo() -> int
a nullary function, returns 0 every time
Zważywszy, że nie mamy tego samego mechanizmu przekazywania tych samych informacji lambdom:
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda (...)
Ale możemy je zhakować na:
>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda ) -> in
nullary -> 0
Ale prawdopodobnie jest jakiś błąd, który zakłóca wynik pomocy.
Lambdy mogą zwracać tylko wyrażenie
Lambdy nie mogą zwracać złożonych instrukcji, tylko wyrażenia.
>>> lambda: if True: 0
File "<stdin>", line 1
lambda: if True: 0
^
SyntaxError: invalid syntax
Wyrażenia mogą być wprawdzie dość złożone, a jeśli bardzo się postarasz, prawdopodobnie możesz osiągnąć to samo za pomocą lambdy, ale dodatkowa złożoność bardziej szkodzi pisaniu czystego kodu.
Używamy Pythona dla przejrzystości i łatwości konserwacji. Nadużywanie lambd może temu przeciwdziałać.
Tylko góry za lambdas: mogą być tworzone w jednym wyrażeniu
To jedyna możliwa korzyść. Ponieważ możesz utworzyć lambdę za pomocą wyrażenia, możesz utworzyć ją wewnątrz wywołania funkcji.
Utworzenie funkcji wewnątrz wywołania funkcji pozwala uniknąć (niedrogiego) wyszukiwania nazwy w porównaniu z wyszukiwaniem utworzonym gdzie indziej.
Jednak ponieważ Python jest ściśle oceniany, nie ma innego zwiększenia wydajności poza unikaniem wyszukiwania nazwy.
Dla bardzo prostego wyrażenia mógłbym wybrać lambdę.
Zwykle używam również lambd podczas interaktywnego Pythona, aby uniknąć wielu wierszy, gdy to zrobi. Używam następującego formatu kodu, gdy chcę przekazać argument do konstruktora podczas wywoływania timeit.repeat
:
import timeit
def return_nullary_lambda(return_value=0):
return lambda: return_value
def return_nullary_function(return_value=0):
def nullary_fn():
return return_value
return nullary_fn
I teraz:
>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304
Uważam, że powyższą niewielką różnicę czasu można przypisać wyszukiwaniu nazw w return_nullary_function
- zwróć uwagę, że jest to bardzo pomijalne.
Wniosek
Lambdy są dobre w nieformalnych sytuacjach, w których chcesz zminimalizować liczbę wierszy kodu na rzecz tworzenia osobnych punktów.
Lambdy są złe w bardziej formalnych sytuacjach, w których potrzebujesz jasności dla redaktorów kodu, którzy przyjdą później, zwłaszcza w przypadkach, gdy są one nietrywialne.
Wiemy, że mamy nadawać naszym przedmiotom dobre nazwy. Jak możemy to zrobić, gdy obiekt nie ma nazwy?
Z tych wszystkich powodów generalnie wolę tworzyć funkcje za pomocą def
zamiast with lambda
.
lambda
, ale nie zgadzam się, że jest „bardzo rzadko”, to jest wspólne dla kluczowych funkcji,sorted
lubitertools.groupby
etc., na przykładsorted(['a1', 'b0'], key= lambda x: int(x[1]))