Quale è più preferibile usare: funzioni lambda o funzioni annidate ('def')?


102

Uso principalmente funzioni lambda ma a volte uso funzioni annidate che sembrano fornire lo stesso comportamento.

Ecco alcuni esempi banali in cui funzionalmente fanno la stessa cosa se uno dei due fosse trovato all'interno di un'altra funzione:

Funzione lambda

>>> a = lambda x : 1 + x
>>> a(5)
6

Funzione annidata

>>> def b(x): return 1 + x

>>> b(5)
6

Ci sono vantaggi nell'usarne uno rispetto all'altro? (Prestazioni? Leggibilità? Limitazioni? Coerenza? Ecc.)

Ha importanza? In caso contrario, ciò viola il principio pitonico:

Dovrebbe esserci un modo ovvio, e preferibilmente uno solo, per farlo .

Risposte:


106

Se è necessario assegnare il lambdaa un nome, utilizzare definvece a. defsono solo zucchero sintattico per un compito, quindi il risultato è lo stesso e sono molto più flessibili e leggibili.

lambdas può essere utilizzato una sola volta, elimina le funzioni che non avranno un nome.

Tuttavia, questo caso d'uso è molto raro. Raramente è necessario passare in giro oggetti funzione senza nome.

I builtin map()e filter()richiedono oggetti funzione, ma elencano le comprensioni e le espressioni del generatore sono generalmente più leggibili di quelle funzioni e possono coprire tutti i casi d'uso, senza bisogno di lambda.

Per i casi in cui hai davvero bisogno di un piccolo oggetto funzione, dovresti usare le operatorfunzioni del modulo, come operator.addinvece dilambda x, y: x + y

Se hai ancora bisogno di alcuni lambdanon coperti, potresti prendere in considerazione la possibilità di scrivere a def, solo per essere più leggibili. Se la funzione è più complessa di quelle al operatormodulo, a defè probabilmente migliore.

Quindi, i buoni lambdacasi d'uso del mondo reale sono molto rari.


9
Sono d'accordo con la risposta su quando utilizzarlo lambda, ma non sono d'accordo sul fatto che sia "molto raro", è comune per le funzioni chiave sortedo itertools.groupbyecc., Ad esempiosorted(['a1', 'b0'], key= lambda x: int(x[1]))
Chris_Rands

30

In pratica, per me ci sono due differenze:

Il primo riguarda cosa fanno e cosa restituiscono:

  • def è una parola chiave che non restituisce nulla e crea un "nome" nello spazio dei nomi locale.

  • lambda è una parola chiave che restituisce un oggetto funzione e non crea un "nome" nello spazio dei nomi locale.

Quindi, se devi chiamare una funzione che accetta un oggetto funzione, l'unico modo per farlo in una riga di codice Python è con un lambda. Non c'è equivalente con def.

In alcuni framework questo è in realtà abbastanza comune; ad esempio, uso molto Twisted e quindi faccio qualcosa di simile

d.addCallback(lambda result: setattr(self, _someVariable, result))

è abbastanza comune e più conciso con lambda.

La seconda differenza riguarda ciò che la funzione effettiva può fare.

  • Una funzione definita con "def" può contenere qualsiasi codice Python
  • Una funzione definita con 'lambda' deve valutare un'espressione e quindi non può contenere istruzioni come print, import, raise, ...

Per esempio,

def p(x): print x

funziona come previsto, mentre

lambda x: print x

è un SyntaxError.

Naturalmente, ci sono soluzioni alternative: sostituire printcon sys.stdout.writeo importcon __import__. Ma di solito è meglio che tu scelga una funzione in questo caso.


23

In questa intervista, Guido van Rossum dice che vorrebbe non aver lasciato "lambda" in Python:

" D. Quale caratteristica di Python ti piace di meno?

A volte sono stato troppo veloce nell'accettare i contributi e in seguito ho capito che si trattava di un errore. Un esempio potrebbero essere alcune delle caratteristiche di programmazione funzionale, come le funzioni lambda. Lambda è una parola chiave che ti consente di creare una piccola funzione anonima; funzioni integrate come map, filter e reduce eseguono una funzione su un tipo di sequenza, come un elenco.

In pratica, non è andata così bene. Python ha solo due ambiti: locale e globale. Questo rende difficile scrivere funzioni lambda, perché spesso vuoi accedere alle variabili in l'ambito in cui è stato definito lambda, ma non è possibile a causa dei due ambiti. C'è un modo per aggirare questo, ma è una specie di fiasco. Spesso sembra molto più facile in Python usare solo un ciclo for invece di scherzare con funzioni lambda map e friends funzionano bene solo quando esiste già una funzione incorporata che fa quello che vuoi.

IMHO, Iambdas può essere conveniente a volte, ma di solito è conveniente a scapito della leggibilità. Puoi dirmi cosa fa:

str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]

L'ho scritto e mi ci è voluto un minuto per capirlo. Questo è di Project Euler - non dirò quale problema perché odio gli spoiler, ma funziona in 0,124 secondi :)


20
Si noti che l'intervista è piuttosto vecchia e Python ha aggiunto da tempo ambiti annidati, il che rende l'argomento che fornisce contro lambda non più rilevante. Sono sicuro che si rammarica ancora di lambda, ma non abbastanza per rimuoverlo in Python 3.0.
Thomas Wouters,

10
In realtà il tuo esempio dovrebbe essere un argomento contro le battute, non le espressioni lambda. Inoltre, avresti dovuto usare la funzione di somma incorporata invece di ridurre con un lambda: str (sum (map (lambda x: x ** x, range (1001)))) [: - 10]
Triptych

2
@ThomasWouters: capisco che lambdanon essere rimosso nella 3.0 fosse una cosa vicina e che Guido non stesse lottando per mantenerlo.
Ethan Furman

11

Per n = 1000, ecco un po 'di tempo per chiamare una funzione rispetto a un lambda:

In [11]: def f(a, b):
             return a * b

In [12]: g = lambda x, y: x * y

In [13]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    f(a, b)
   ....:
100 loops, best of 3: 285 ms per loop

In [14]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    g(a, b)
   ....:
100 loops, best of 3: 298 ms per loop

In [15]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    (lambda x, y: x * y)(a, b)
   ....:
100 loops, best of 3: 462 ms per loop

3
È interessante vedere che le versioni lambda e definite sono più o meno equivalenti. L'ultimo test ha richiesto più tempo perché probabilmente Python aveva bisogno di allocare spazio ogni volta che ha definito quella funzione lambda.
hlin117

Immagino che questo abbia senso in quanto la definizione potrebbe fare riferimento a variabili locali (che potrebbero essere cambiate) ... anche se nel caso in cui non lo facesse, come qui, cpython potrebbe fare un lavoro migliore.
Andy Hayden

Usa dis.dis; Il tuo (lambda x, y: x * y) crea la funzione ogni ciclo. Se crei il lambda prima del ciclo (noto anche come f = lambda x, y: x * y), il bytecode per chiamare la funzione sarà esattamente lo stesso di g / f nell'esempio precedente, quindi le prestazioni lambda sono le stesse come funzione def. Quindi lambda o def non hanno alcun impatto se lo usi lo stesso. Fai l'inverso, dichiara la funzione f () all'interno del ciclo, quindi chiamala ...
tito

@tito Credo che sia esattamente ciò che dimostrano i 3 esempi a tempo ...
Andy Hayden

@tito oh, stai dicendo di definire la funzione nel ciclo, certo, ma direi che è uno schema insolito. Non sono sicuro del motivo per cui questo avesse bisogno del voto negativo su quel commento ...
Andy Hayden

7

Prestazione:

Creare una funzione con lambdaè leggermente più veloce che crearla con def. La differenza è dovuta alla defcreazione di una voce di nome nella tabella locals. La funzione risultante ha la stessa velocità di esecuzione.


Leggibilità:

Le funzioni Lambda sono un po 'meno leggibili per la maggior parte degli utenti Python, ma anche molto più concise in alcune circostanze. Considera la conversione dall'utilizzo di routine non funzionali a funzionali:

# Using non-functional version.

heading(math.sqrt(v.x * v.x + v.y * v.y), math.atan(v.y / v.x))

# Using lambda with functional version.

fheading(v, lambda v: math.sqrt(v.x * v.x + v.y * v.y), lambda v: math.atan(v.y / v.x))

# Using def with functional version.

def size(v):
    return math.sqrt(v.x * v.x + v.y * v.y)

def direction(v):
    return math.atan(v.y / v.x)

deal_with_headings(v, size, direction)

Come puoi vedere, la lambdaversione è più breve e "più semplice", nel senso che devi solo aggiungere lambda v:alla versione originale non funzionale per convertirla nella versione funzionale. È anche molto più conciso. Ma ricorda, molti utenti di Python saranno confusi dalla sintassi lambda, quindi ciò che perdi in lunghezza e complessità reale potrebbe essere recuperato nella confusione dagli altri programmatori.


Limitazioni:

  • lambda le funzioni possono essere utilizzate solo una volta, a meno che non siano assegnate a un nome di variabile.
  • lambdale funzioni assegnate ai nomi delle variabili non hanno alcun vantaggio rispetto alle deffunzioni.
  • lambda le funzioni possono essere difficili o impossibili da decapare.
  • def i nomi delle funzioni devono essere scelti con cura per essere ragionevolmente descrittivi e univoci o perlomeno non utilizzati in altro modo nell'ambito.

Consistenza:

Python evita principalmente le convenzioni di programmazione funzionale a favore di una semantica oggettiva procedurale e più semplice. L' lambdaoperatore è in diretto contrasto con questo pregiudizio. Inoltre, in alternativa al già prevalente def, illambda funzione aggiunge diversità alla sintassi. Alcuni lo considererebbero meno coerente.


Funzioni preesistenti:

Come notato da altri, molti usi di lambdain the field possono essere sostituiti da membri di operatoro altri moduli. Per esempio:

do_something(x, y, lambda x, y: x + y)
do_something(x, y, operator.add)

L'utilizzo della funzione preesistente può rendere il codice più leggibile in molti casi.


Il principio pitonico: "Ci dovrebbe essere un modo ovvio, e preferibilmente solo uno, per farlo"

È simile all'unica fonte della dottrina della verità . Sfortunatamente, il principio dell'unica via ovvia per farlo è sempre stato più un'aspirazione malinconica per Python, piuttosto che un vero principio guida. Considera la potente comprensione degli array in Python. Sono funzionalmente equivalenti alle funzioni mape filter:

[e for e in some_array if some_condition(e)]
filter(some_array, some_condition)

lambdae defsono la stessa cosa.

È una questione di opinione, ma direi che qualsiasi cosa nel linguaggio Python inteso per uso generale che ovviamente non rompa nulla è abbastanza "Pythonic".


7

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, foopuò essere cercato con codice serializzato, mentre lnon 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 foobene, 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 fooper 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 defanziché con lambda.


6

Sono d'accordo con il consiglio di nosklo: se devi dare un nome alla funzione, usa def. Riservo le lambdafunzioni per i casi in cui sto solo passando un breve frammento di codice a un'altra funzione, ad esempio:

a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )

3
Nella maggior parte delle combinazioni di map / lambda, puoi sostituirlo con una lista di comprensione o una funzione più appropriata. Ad esempio, "map (sum, a)" o "[x [0] + x [1] for x in a]"
John Millikin,

Sì è vero. A volte però preferisco map (). Questo era per lo più solo un esempio artificioso dell'utilizzo di una funzione in linea.
Dan Lenski

esattamente ... La maggior parte degli esempi sono inventati, perché è innaturale da usare e ci sono modi migliori pratici nella maggior parte dei casi.
nosklo

5

Pur essendo d'accordo con le altre risposte, a volte è più leggibile. Ecco un esempio in cui lambdaè utile, in un caso d'uso continuo a incontrare di un N dimensionale defaultdict.
Ecco un esempio:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)

Lo trovo più leggibile rispetto alla creazione di una defper la seconda dimensione. Questo è ancora più significativo per le dimensioni superiori.


from functools import partial; defaultdict(partial(defaultdict, list)). Assegna il parziale a un nome se vuoi usarlo più di una volta. Ma se continui a incontrare questo costrutto, significa che non sei ASCIUTTO. Fattorizzalo in una libreria di utilità. È possibile utilizzare questo costrutto per creare un defaultdict arbitario n-dimensionale utilizzando altri functools (o un ciclo o ricorsione).
DylanYoung

3

L'uso principale di lambda è sempre stato per semplici funzioni di callback e per map, reduce, filter, che richiedono una funzione come argomento. Con le comprensioni di lista che diventano la norma e l'aggiunta consentita se come in:

x = [f for f in range(1, 40) if f % 2]

è difficile immaginare un caso reale per l'uso della lambda nell'uso quotidiano. Di conseguenza, direi, evita lambda e crea funzioni annidate.


3

Un'importante limitazione dei lambda è che non possono contenere nient'altro che un'espressione. È quasi impossibile per un'espressione lambda produrre qualcosa di diverso da banali effetti collaterali, poiché non può avere un corpo così ricco come undef funzione ed.

Detto questo, Lua ha influenzato il mio stile di programmazione verso l'ampio uso di funzioni anonime, e io ho disseminato il mio codice con esse. Inoltre, tendo a pensare a map / reduce come operatori astratti in modi che non considero comprensori di elenchi o generatori, quasi come se rimandassi esplicitamente una decisione di implementazione utilizzando quegli operatori.

Modificare: questa è una domanda piuttosto vecchia e le mie opinioni sull'argomento sono leggermente cambiate.

Prima di tutto, sono fortemente prevenuto contro l'assegnazione di lambdaun'espressione a una variabile; poiché python ha una sintassi speciale proprio per questo (suggerimento, def). Inoltre, molti degli usi di lambda, anche quando non ricevono un nome, hanno implementazioni predefinite (e più efficienti). Ad esempio, l'esempio in questione può essere abbreviato in just (1).__add__, senza la necessità di racchiuderlo in un lambdao def. Molti altri usi comuni possono essere soddisfatti con alcune combinazioni dei moduli operator, itertoolse functools.


1
(1).__add__- chiamare direttamente i metodi dunder non dovrebbe quasi mai accadere. Mille lambdas per ogni chiamata diretta.
Ethan Furman

1
@ EthanFurman: Beh, nella mia esperienza, i richiami della natura (1).__add__sono in qualche modo rari, ma non mi avvicinerei affatto a "dovrebbe". senza dubbio, trovo che il primo sia di gran lunga più leggibile lambda x: 1 + x. Se avessimo qualcosa di più simile alla notazione haskells slice, (1+)sarebbe fantastico, ma dobbiamo accontentarci di ciò che è semanticamente esattamente quella cosa, il nome del metodo dunder.
SingleNegationElimination

2
  • Tempo di calcolo.
  • Funzione senza nome.
  • Per ottenere una funzione e molti usano la funzionalità.

Considerando un semplice esempio,

# CREATE ONE FUNCTION AND USE IT TO PERFORM MANY OPERATIONS ON SAME TYPE OF DATA STRUCTURE.
def variousUse(a,b=lambda x:x[0]):
    return [b(i) for i in a]

dummyList = [(0,1,2,3),(4,5,6,7),(78,45,23,43)]
variousUse(dummyList)                           # extract first element
variousUse(dummyList,lambda x:[x[0],x[2],x[3]]) # extract specific indexed element
variousUse(dummyList,lambda x:x[0]+x[2])        # add specific elements
variousUse(dummyList,lambda x:x[0]*x[2])        # multiply specific elements

1

Se hai intenzione di assegnare il lambda a una variabile nell'ambito locale, puoi anche usare def perché è più leggibile e può essere espanso più facilmente in futuro:

fun = lambda a, b: a ** b # a pointless use of lambda
map(fun, someList)

o

def fun(a, b): return a ** b # more readable
map(fun, someList)

Entrambi from operator import pow;map(pow, someList)e (a**b for a,b in someList)sono ancora più leggibili.
InQβ

1

Un uso per lambda che ho trovato ... è nei messaggi di debug.

Poiché i lambda possono essere valutati pigramente, puoi avere un codice come questo:

log.debug(lambda: "this is my message: %r" % (some_data,))

invece di possibilmente costoso:

log.debug("this is my message: %r" % (some_data,))

che elabora la stringa di formato anche se la chiamata di debug non produce output a causa del livello di registrazione corrente.

Ovviamente, affinché funzioni come descritto, il modulo di registrazione in uso deve supportare lambda come "parametri pigri" (come fa il mio modulo di registrazione).

La stessa idea può essere applicata a qualsiasi altro caso di valutazione pigra per la creazione di valore dei contenuti su richiesta.

Ad esempio questo operatore ternario personalizzato:

def mif(condition, when_true, when_false):
    if condition:
         return when_true()
    else:
         return when_false()

mif(a < b, lambda: a + a, lambda: b + b)

invece di:

def mif(condition, when_true, when_false):
    if condition:
         return when_true
    else:
         return when_false

mif(a < b, a + a, b + b)

con lambda verrà valutata solo l'espressione selezionata dalla condizione, senza lambda verranno valutate entrambe.

Ovviamente potresti semplicemente usare funzioni invece di lambda, ma per espressioni brevi i lambda sono (c) più snelli.


1
NB loggingha già una formattazione pigra: log.debug("this is my message: %r", some_data)formatterà solo quando / se il messaggio è richiesto.
j08lue

@ j08lue il metodo lambda salta la valutazione di tutto nel caso in cui l'output di debug non venga prodotto, nel caso in cui mostri some_datapotrebbe essere un'espressione costosa o una chiamata a funzione / metodo.
Glushiator

0

Sono d'accordo con nosklo. A proposito, anche con una funzione usa una volta, usa e getta , la maggior parte delle volte vuoi semplicemente usare qualcosa dal modulo operatore.

PER ESEMPIO :

Hai una funzione con questa firma: myFunction (data, funzione di callback).

Vuoi passare una funzione che aggiunge 2 elementi.

Utilizzando lambda:

myFunction(data, (lambda x, y : x + y))

Il modo pitonico:

import operator
myFunction(data, operator.add)

Ovviamente questo è un semplice esempio, ma ci sono molte cose che il modulo operatore fornisce, inclusi gli elementi setters / getter per list e dict. Veramente cool.


-1

Una delle principali differenze è che non è possibile utilizzare le deffunzioni inline, che è a mio parere il caso d'uso più conveniente per una lambdafunzione. Ad esempio, quando si ordina un elenco di oggetti:

my_list.sort(key=lambda o: o.x)

Suggerirei quindi di mantenere l'uso di lambda per questo tipo di operazioni banali, che inoltre non traggono vantaggio dalla documentazione automatica fornita nominando la funzione.


-2

lambda è utile per generare nuove funzioni:

>>> def somefunc(x): return lambda y: x+y
>>> f = somefunc(10)
>>> f(2)
12
>>> f(4)
14
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.