Quale è più preferibile usare in Python: funzioni lambda o funzioni nidificate ('def')?

StackOverflow https://stackoverflow.com/questions/134626

Domanda

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

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

Funzione lambda

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

Funzione nidificata

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

>>> b(5)
6

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

Importa anche? In caso contrario, ciò viola il principio Pythonic:

  

& # 8220; Dovrebbe esserci uno & # 8212; e preferibilmente solo uno & # 8212 ; modo ovvio per farlo & # 8221; .

È stato utile?

Soluzione

Se devi assegnare lambda a un nome, usa invece un def . I def sono solo zucchero sintattico per un compito, quindi il risultato è lo stesso e sono molto più flessibili e leggibili.

lambda può essere usato per usare una volta, buttare via funzioni che non avranno un nome.

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

Il map () e il filter () integrati hanno bisogno di oggetti funzione, ma comprensione dell'elenco e espressioni del generatore sono generalmente più leggibili di quelle funzioni e possono coprire tutti i casi d'uso, senza la necessità di lambda.

Per i casi in cui hai davvero bisogno di un piccolo oggetto funzione, dovresti usare le funzioni del modulo operator , come operator.add invece di lambda x, y: x + y

Se hai ancora bisogno di un lambda non coperto, potresti considerare di scrivere un def , solo per essere più leggibile. Se la funzione è più complessa di quelle presenti nel modulo operator , è probabilmente meglio una def .

Quindi, i casi d'uso lambda nel mondo reale sono molto rari.

Altri suggerimenti

In pratica, per me ci sono due differenze:

Il primo riguarda ciò che fanno e ciò che 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 hai bisogno di chiamare una funzione che accetta un oggetto funzione, l'unico modo per farlo in una riga di codice Python è con un lambda. Non esiste un equivalente con def.

In alcuni framework questo è in realtà abbastanza comune; ad esempio, utilizzo Twisted e quindi faccio qualcosa del genere

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

è abbastanza comune e più conciso con lambdas.

La seconda differenza riguarda ciò che la funzione effettiva è autorizzata a 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, ...

Ad esempio,

def p(x): print x

funziona come previsto, mentre

lambda x: print x

è un SyntaxError.

Naturalmente, ci sono soluzioni alternative: sostituisci stampa con sys.stdout.write o import con __import __ . Ma di solito è meglio andare con una funzione in quel caso.

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

  

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

  A volte sono stato troppo veloce nell'accettare i contributi e in seguito mi sono reso conto che si trattava di un errore. Un esempio potrebbe essere alcune delle funzionalità di programmazione funzionale, come le funzioni lambda. lambda è una parola chiave che ti consente di creare una piccola funzione anonima; funzioni integrate come mappa, filtro e riduzione 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. Ciò rende dolorosa la scrittura delle funzioni lambda, poiché spesso si desidera accedere alle variabili nell'ambito in cui è stata definita la lambda, ma non è possibile a causa dei due ambiti. C'è un modo per aggirare questo, ma è una specie di kludge. Spesso sembra molto più facile in Python usare solo un ciclo for invece di giocare con le funzioni lambda. map e gli amici funzionano bene solo quando esiste già una funzione integrata che fa ciò che desideri.

IMHO, Iambdas può essere utile a volte, ma di solito sono convenienti a spese della leggibilità. Puoi dirmi cosa fa questo:

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 proviene da Project Euler - non dirò quale problema perché odio gli spoiler, ma funziona in 0,124 secondi :)

Per n = 1000 ecco un po 'di tempo per chiamare una funzione contro 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

Sono d'accordo con il consiglio di nosklo: se devi dare un nome alla funzione, usa def . Riservo le funzioni lambda per i casi in cui sto 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 )

Performance:

La creazione di una funzione con lambda è leggermente più veloce rispetto alla creazione con def . La differenza è dovuta alla def che crea una voce nella tabella dei locali. La funzione risultante ha la stessa velocità di esecuzione.


Leggibilità:

Le funzioni Lambda sono in qualche modo meno leggibili per la maggior parte degli utenti Python, ma anche molto più concise in alcune circostanze. Prendi in considerazione la conversione dall'uso di routine non funzionale a funzionale:

# 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 versione lambda è più corta e " più facile " nel senso che devi solo aggiungere lambda v: alla versione originale non funzionale per convertirla in versione funzionale. È anche molto più conciso. Ma ricorda, molti utenti di Python saranno confusi dalla sintassi lambda, quindi ciò che perdi in termini di lunghezza e complessità reale potrebbe essere recuperato in confusione dai colleghi programmatori.


Limitazioni:

    Le funzioni
  • lambda possono essere utilizzate una sola volta, a meno che non vengano assegnate a un nome di variabile.
  • Le funzioni
  • lambda assegnate ai nomi delle variabili non hanno alcun vantaggio rispetto alle funzioni def .
  • Le funzioni
  • lambda possono essere difficili o impossibili da decapitare.
  • I nomi delle funzioni
  • def devono essere scelti con cura per essere ragionevolmente descrittivi e univoci o almeno non utilizzati nell'ambito.

Consistenza:

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


Funzioni preesistenti:

Come notato da altri, molti usi di lambda nel campo possono essere sostituiti da membri dell'operatore o da altri moduli. Ad esempio:

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

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


Il principio Pythonic: & # 8220; Dovrebbe esserci un & # 8212; e preferibilmente solo un & # 8212; modo ovvio per farlo & # 8221;

È simile alla singola fonte di verità . Sfortunatamente, il principio del solo ovvio modo di fare è sempre stato più un'aspirazione malinconica per Python, piuttosto che un vero principio guida. Considera le potentissime comprensioni dell'array in Python. Sono funzionalmente equivalenti alle funzioni map e filter :

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

lambda e def sono gli stessi.

È una questione di opinione, ma direi che qualsiasi cosa nel linguaggio Python destinata all'uso generale che ovviamente non rompe nulla è "Pythonic" sufficiente.

Pur concordando con le altre risposte, a volte è più leggibile. Ecco un esempio in cui lambda è utile, in un caso d'uso continuo a incontrare una dimensione N 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 un def per la seconda dimensione. Ciò è ancora più significativo per dimensioni superiori.

L'uso primario di lambda è sempre stato per semplici funzioni di callback e per mappare, ridurre, filtrare, che richiedono una funzione come argomento. Con la comprensione dell'elenco che diventa 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 di lambda nell'uso quotidiano. Di conseguenza, direi, evita lambda e crea funzioni nidificate.

Un'importante limitazione dei lambda è che non possono contenere nulla oltre a un'espressione. È quasi impossibile per un'espressione lambda produrre qualsiasi cosa oltre a effetti collaterali banali, dal momento che non può avere un corpo così vicino come una funzione ed def .

Detto questo, Lua ha influenzato il mio stile di programmazione verso l'uso estensivo di funzioni anonime, e io sporcano il mio codice con loro. Inoltre, tendo a pensare a mappare / ridurre come operatori astratti in modi che non considero comprensioni o generatori di elenchi, quasi come se rinviassi esplicitamente una decisione di implementazione usando quegli operatori.

Modifica: Questa è una domanda piuttosto vecchia e le mie opinioni in merito sono cambiate, in qualche modo.

Prima di tutto, sono fortemente contrario a assegnare un'espressione lambda a una variabile; poichè python ha una sintassi speciale proprio per questo (suggerimento, def ). Inoltre, molti degli usi di lambda, anche quando non hanno un nome, hanno implementazioni predefinite (e più efficienti). Ad esempio, l'esempio in questione può essere abbreviato in solo (1) .__ add__ , senza la necessità di avvolgerlo in un lambda o def . Molti altri usi comuni possono essere soddisfatti con una combinazione dei moduli operator , itertools e functools .

  

Più preferibile: funzioni lambda o funzioni nidificate ( def )?

C'è un vantaggio nell'usare un lambda rispetto a una normale funzione (sono creati in un'espressione) e diversi inconvenienti. Per questo motivo, preferisco creare funzioni con la parola chiave def anziché con lambdas.

Primo punto: sono lo stesso tipo di oggetto

Un lambda produce lo stesso tipo di oggetto di una normale funzione

>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
... 
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True

Poiché le lambda sono funzioni, sono oggetti di prima classe.

Sia lambda che funzioni:

  • può essere passato come argomento (come una normale funzione)
  • quando creato all'interno di una funzione esterna diventa una chiusura rispetto ai locali delle funzioni esterne

Ma, per impostazione predefinita, alle lambda mancano alcune cose che le funzioni ottengono tramite la sintassi della definizione di funzione completa.

Il __name__ di una lamba è '<lambda>'

Le lambda sono funzioni anonime, dopotutto, quindi non conoscono il loro nome.

>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'

Quindi le lambda non possono essere cercate a livello di codice nel loro spazio dei nomi.

Questo limita alcune 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 pippo bene, perché conosce il suo nome:

>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>

Le Lambdas non hanno annotazioni né 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 a lambdas:

>>> 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 a rovinare l'output di aiuto.

Lambdas può solo restituire un'espressione

Lambdas 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 essere certamente piuttosto complesse, e se provi molto puoi probabilmente fare lo stesso con un lambda, ma la complessità aggiunta è più un danno per scrivere un codice chiaro.

Usiamo Python per chiarezza e manutenibilità. L'uso eccessivo di lambda può andare contro questo.

Il unico vantaggio di lambdas: può essere creato in una singola espressione

Questo è l'unico vantaggio possibile. Poiché è possibile creare un lambda con un'espressione, è possibile crearlo all'interno di una chiamata di funzione.

La creazione di una funzione all'interno di una chiamata di funzione evita la ricerca (economica) del nome rispetto a una creata altrove.

Tuttavia, poiché Python viene valutato rigorosamente, non vi sono altri vantaggi in termini di prestazioni oltre a evitare la ricerca del nome.

Per un'espressione molto semplice, potrei scegliere un lambda.

Tendo anche ad usare lambdas quando faccio Python interattivo, per evitare più righe quando lo si farà. Uso il seguente tipo di formato di codice quando voglio passare un argomento a un costruttore quando chiamo 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 ora:

>>> 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

Le lambda sono utili per situazioni informali in cui si desidera ridurre al minimo le righe di codice a favore della creazione di un punto singolare.

Le lambda sono dannose per 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 un buon nome ai nostri oggetti. Come possiamo farlo quando l'oggetto non ha nessun nome?

Per tutti questi motivi, preferisco creare funzioni con def anziché con lambda .

  • Tempo di calcolo.
  • Funzione senza nome.
  • Per ottenere una funzione e molte 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

Se hai intenzione di assegnare 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)

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

Poiché lambdas può essere valutato pigramente, puoi avere un codice come questo:

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

anziché probabilmente 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.

Naturalmente per funzionare come descritto il modulo di registrazione in uso deve supportare lambdas 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 del contenuto on demand.

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)

anziché:

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 valutati entrambi.

Ovviamente potresti semplicemente usare le funzioni invece di lambdas, ma per le espressioni brevi le lambdas sono (c) più snelle.

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

E.G:

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

Vuoi passare una funzione che aggiunge 2 elementi.

Uso di 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 setter / getter per list e dict. Davvero fantastico.

lambda è utile per generare nuove funzioni:

def somefunc(x): return lambda y: x+y
f = somefunc(10)
f(2)
>>> 12
f(4)
>>> 14

Una grande differenza è che non è possibile utilizzare le funzioni def in linea, che è a mio avviso il caso d'uso più conveniente per una funzione lambda . 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 beneficiano realmente della documentazione automatica fornita nominando la funzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top