Più preferibile: funzioni lambda o funzioni annidate ( def
)?
C'è un vantaggio nell'usare un lambda rispetto a una funzione regolare: vengono creati in un'espressione.
Ci sono diversi inconvenienti:
- nessun nome (solo
'<lambda>'
)
- nessuna docstring
- nessuna annotazione
- nessuna dichiarazione complessa
Sono anche entrambi lo stesso tipo di oggetto. Per questi motivi, generalmente preferisco creare funzioni con l'estensionedef
parola chiave anziché con lambda.
Primo punto: sono lo stesso tipo di oggetto
Un lambda restituisce lo stesso tipo di oggetto di una funzione normale
>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
...
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True
Poiché i lambda sono funzioni, sono oggetti di prima classe.
Sia lambda che funzioni:
- può essere passato come argomento (come una normale funzione)
- quando viene creato all'interno di una funzione esterna diventa una chiusura sui locali delle funzioni esterne
Ma i lambda, per impostazione predefinita, mancano di alcune cose che le funzioni ottengono tramite la sintassi della definizione di funzione completa.
Un lamba __name__
è'<lambda>'
I Lambda sono funzioni anonime, dopotutto, quindi non conoscono il proprio nome.
>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'
Pertanto, i lambda non possono essere cercati a livello di codice nel loro spazio dei nomi.
Questo limita certe cose. Ad esempio, foo
può essere cercato con codice serializzato, mentre l
non può:
>>> 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
Possiamo cercare foo
bene, perché conosce il proprio nome:
>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>
Lambda non ha annotazioni e nessuna docstring
Fondamentalmente, i lambda non sono documentati. Riscriviamo foo
per essere meglio documentati:
def foo() -> int:
"""a nullary function, returns 0 every time"""
return 0
Ora, foo ha la documentazione:
>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:
foo() -> int
a nullary function, returns 0 every time
Considerando che, non abbiamo lo stesso meccanismo per fornire le stesse informazioni ai lambda:
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda (...)
Ma possiamo hackerarli su:
>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda ) -> in
nullary -> 0
Ma probabilmente c'è qualche errore che incasina l'output di aiuto, però.
Lambdas può restituire solo un'espressione
Lambda non può restituire istruzioni complesse, solo espressioni.
>>> lambda: if True: 0
File "<stdin>", line 1
lambda: if True: 0
^
SyntaxError: invalid syntax
Le espressioni possono certamente essere piuttosto complesse e, se ci provi molto, probabilmente puoi ottenere lo stesso risultato con un lambda, ma la complessità aggiunta è più dannosa per la scrittura di codice chiaro.
Usiamo Python per chiarezza e manutenibilità. L'uso eccessivo di lambda può funzionare contro questo.
L' unico vantaggio per le lambda: può essere creato in una singola espressione
Questo è l'unico vantaggio possibile. Poiché puoi creare un lambda con un'espressione, puoi crearlo all'interno di una chiamata di funzione.
La creazione di una funzione all'interno di una chiamata di funzione evita la ricerca del nome (poco costosa) rispetto a quella creata altrove.
Tuttavia, poiché Python viene valutato rigorosamente, non c'è altro guadagno in termini di prestazioni per farlo a parte evitare la ricerca del nome.
Per un'espressione molto semplice, potrei scegliere un lambda.
Tendo anche a usare lambda quando eseguo Python interattivo, per evitare più righe quando una lo farà. Uso il seguente tipo di formato di codice quando voglio passare un argomento a un costruttore durante la chiamata 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
E adesso:
>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304
Credo che la leggera differenza di tempo sopra possa essere attribuita alla ricerca del nome in return_nullary_function
- nota che è molto trascurabile.
Conclusione
I lambda sono utili per situazioni informali in cui si desidera ridurre al minimo le righe di codice a favore di un punto singolare.
I lambda sono dannosi per le situazioni più formali in cui è necessaria chiarezza per gli editor di codice che verranno dopo, specialmente nei casi in cui non sono banali.
Sappiamo che dovremmo dare ai nostri oggetti dei bei nomi. Come possiamo farlo quando l'oggetto non ha nome?
Per tutti questi motivi, generalmente preferisco creare funzioni con def
anziché con lambda
.
lambda
, ma non sono d'accordo sul fatto che sia "molto raro", è comune per le funzioni chiavesorted
oitertools.groupby
ecc., Ad esempiosorted(['a1', 'b0'], key= lambda x: int(x[1]))