Nessuna Lambda multilinea in Python: perché no?


335

Ho sentito dire che Lambda multilinea non può essere aggiunto in Python perché si scontrerebbero sintatticamente con gli altri costrutti di sintassi in Python. Oggi stavo pensando a questo sul bus e mi sono reso conto che non potevo pensare a un singolo costrutto Python con cui si scontrano le lambda multilinea. Dato che conosco abbastanza bene la lingua, questo mi ha sorpreso.

Ora, sono sicuro che Guido avesse una ragione per non includere lambda multilinea nella lingua, ma per curiosità: qual è una situazione in cui includere una lambda multilinea sarebbe ambiguo? Ciò che ho sentito è vero o c'è qualche altra ragione per cui Python non ammette lambda multilinea?


12
tl; versione dr: perché Python è un linguaggio pigro senza {} blocchi e quindi questo non è stato permesso al fine di mantenere un design sintattico coerente.
Andrew,

11
Inoltre: sono completamente sorpreso che nessuno abbia menzionato questo nelle risposte ... Puoi terminare le righe con il carattere \ in Python e passare alla riga successiva ... Queste informazioni sostituiscono in qualche modo l'intera domanda quindi ...
Andrew


"design sintattico"
nicolas,

Ciò richiederebbe l'abilitazione di dichiarazioni all'interno di espressioni. Se hai intenzione di farlo, non hai bisogno di lambdaespressioni in primo luogo; potresti semplicemente usare le defdichiarazioni nelle espressioni.
Chepner

Risposte:


153

Guarda quanto segue:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

È un ritorno lambda (y, [1,2,3])(quindi la mappa ottiene solo un parametro, con conseguente errore)? O ritorna y? O è un errore di sintassi, perché la virgola sulla nuova riga è fuori posto? Come farebbe Python a sapere cosa vuoi?

All'interno delle parentesi, il rientro non ha importanza per Python, quindi non puoi lavorare in modo inequivocabile con le multiline.

Questo è solo uno, probabilmente ci sono più esempi.


107
potrebbero forzare l'uso delle parentesi se si desidera restituire una tupla da una lambda. IMO, questo avrebbe dovuto essere sempre applicato per prevenire tali ambiguità, ma vabbè.
mpen

26
Questa è una semplice ambiguità che deve essere risolta aggiungendo un ulteriore set di parentesi, qualcosa che esiste già in molti luoghi, ad esempio espressioni del generatore circondate da altri argomenti, chiamando un metodo su un valore intero letterale (anche se questo non è necessario dal momento che un il nome della funzione non può iniziare con una cifra) e ovviamente anche lambda a riga singola (che possono essere espressioni lunghe scritte su più righe). I lambda multi-linea non sarebbero particolarmente diversi da questi casi che merita di escluderli su tale base. Questa è la vera risposta
nmclean

3
Mi piace come ci siano lingue gazillion che lo affrontano senza problemi, ma in qualche modo ci sono alcune ragioni profonde per cui è presumibilmente molto difficile se non impossibile
nicolas

1
@nicolas che è pitone in breve
javadba il

Motivo per cui non uso lambda, così poco sviluppato in Python.
NoName

635

Guido van Rossum (l'inventore di Python) risponde a questa domanda esatta in un vecchio post sul blog .
Fondamentalmente, ammette che è teoricamente possibile, ma che qualsiasi soluzione proposta sarebbe non-Pythonic:

"Ma la complessità di qualsiasi soluzione proposta per questo puzzle è immensa, per me: richiede che il parser (o più precisamente, il lexer) sia in grado di passare avanti e indietro tra le modalità sensibili al rientro e non sensibili, mantenendo una pila delle modalità precedenti e del livello di rientro. Tecnicamente tutto ciò può essere risolto (esiste già una serie di livelli di rientro che potrebbero essere generalizzati). Ma nulla di tutto ciò mi toglie la sensazione che si tratti di un elaborato aggeggio di Rube Goldberg . "


108
Perché questa non è la risposta migliore? Non si tratta delle ragioni tecniche, è una scelta progettuale, come chiaramente affermato dall'inventore.
Dan Abramov,

13
@DanAbramov perché probabilmente l'OP non ha effettuato l'accesso per anni.
contratto del Prof. Falken è stato violato il

7
Per coloro che non hanno capito il riferimento di Rube Goldberg, consultare: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj

56
La risposta di Guido è solo un'altra ragione per cui vorrei che Python non dipendesse dal rientro per definire i blocchi.
LS

25
Non sono sicuro di definire la "sensazione viscerale" una scelta progettuale. ;)
Elliot Cameron il

54

Questo è generalmente molto brutto (ma a volte le alternative sono ancora più brutte), quindi una soluzione alternativa è quella di fare un'espressione tra parentesi graffe:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

Tuttavia, non accetterà alcun incarico, quindi dovrai preparare i dati in anticipo. Il posto che ho trovato utile è il wrapper PySide, dove a volte hai brevi callback. Scrivere ulteriori funzioni membro sarebbe ancora più brutto. Normalmente non avrai bisogno di questo.

Esempio:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())

2
Il mio capo stava solo chiedendo qualcosa del genere nella nostra applicazione PyQt. Eccezionale!
TheGerm,

1
Grazie a questo, stavo anche cercando un buon modo per usare lambda brevi (ma ancora multilinea) come callback per la nostra interfaccia utente PySide.
Michael Leonard,

E ora ho visto questo ha suggerito all'istante di usare lambda arge setattr(arg, 'attr','value')sovvertire "nessun compito ...". E poi c'è la valutazione di corto circuito di ande or... è il Javascript che lo fa. Affonda le radici dentro di te, come l'edera in un muro. Spero quasi di dimenticarmene per Natale.
nigel222,

abbastanza intelligente - e abbastanza leggibile. Ora - riguardo a quegli incarichi (mancanti ..) ..
Javavba,

@ nigel222 perché vergognarsi? Il linguaggio Python è sostanzialmente paralizzato, ma è comunque quello usato per gran parte della scienza dei dati . Quindi apportiamo modifiche. Trovare modi per fare effetti collaterali (spesso abbastanza semplicemente per stampare / registrare!) E incarichi (abbastanza spesso solo per alterazioni intermedie!) Dovrebbe essere ben gestito dalla lingua. Ma non sono nemmeno supportati (almeno se segui le PEPlinee guida)
javadba

17

Un paio di collegamenti rilevanti:

Per un po ', stavo seguendo lo sviluppo di Reia, che inizialmente avrebbe avuto la sintassi basata sul rientro di Python con i blocchi di Ruby, il tutto sopra Erlang. Ma il progettista ha smesso di rinunciare alla sensibilità del rientro, e questo post che ha scritto su quella decisione include una discussione sui problemi che ha incontrato con il rientro + blocchi multi-linea e un maggiore apprezzamento che ha guadagnato per le questioni / decisioni di progettazione di Guido:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

Inoltre, ecco una proposta interessante per i blocchi in stile Ruby in Python che mi sono imbattuto in cui Guido pubblica una risposta senza effettivamente eliminarla (non sono sicuro che ci siano stati degli abbattimenti successivi, però):

http://tav.espians.com/ruby-style-blocks-in-python.html


12

[Modifica] Leggi questa risposta.Spiega perché la lambda multilinea non è una cosa.

In poche parole, non è ritonico. Dal post del blog di Guido van Rossum:

Trovo inaccettabile qualsiasi soluzione che incorpori un blocco basato sul rientro nel mezzo di un'espressione. Dato che trovo altrettanto inaccettabile la sintassi alternativa per il raggruppamento di istruzioni (ad esempio parentesi graffe o parole chiave inizio / fine), ciò rende praticamente un lambda multilinea un puzzle irrisolvibile.


10

Lascia che ti presenti un trucco glorioso ma terrificante:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

Ora puoi utilizzare questo LETmodulo come tale:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

che dà: [0, 3, 8]



Questo e spettacolare! Penso che lo userò la prossima volta che scriverò Python. Sono principalmente un programmatore Lisp e JS e la mancanza di lambada multi linea fa male. Questo è un modo per ottenerlo.
Christopher Dumas,

7

Sono colpevole di praticare questo trucco sporco in alcuni dei miei progetti che è un po 'più semplice:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

Spero che tu possa trovare un modo per rimanere pitonico, ma se devi farlo in modo meno doloroso rispetto all'utilizzo di exec e manipolazione di globi.


6

Vorrei provare ad affrontare il problema di analisi di @balpha. Vorrei usare le parentesi attorno al lamda multilinea. Se non ci sono parentesi, la definizione lambda è avida. Quindi la lambda dentro

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

restituisce una funzione che ritorna (y*z, [1,2,3])

Ma

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

si intende

map(func, [1,2,3])

dove func è la lambda multilinea che restituisce y * z. Funziona?


1
Sto pensando che quello in alto dovrebbe tornare map(func, [1,2,3])e quello in basso dovrebbe essere un errore perché la funzione mappa non ha abbastanza argomenti. Inoltre ci sono alcune parentesi extra nel codice.
Samy Bencherif,

rilasciandolo in pycharm eseguendo python2.7.13, si verifica un errore di sintassi.
simbo1905,

parentesi extra
Samy Bencherif

4

(Per chiunque sia ancora interessato all'argomento.)

Considera questo (include anche l'uso dei valori di ritorno delle dichiarazioni in ulteriori dichiarazioni all'interno della lambda "multilinea", sebbene sia brutto fino al punto di vomitare ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12

Questo non funziona quando viene chiamato una seconda volta con parametri diversi e provoca una perdita di memoria a meno che la prima riga non sia state.clear()poiché gli argomenti predefiniti vengono creati una sola volta quando viene creata la funzione.
Matthew D. Scholefield,

1

Puoi semplicemente usare slash ( \) se hai più righe per la tua funzione lambda

Esempio:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30

La domanda riguarda se stessa usando più di 1 espressione anziché più di 1 riga letterale.
Tomas Zubiri,

1

Sto iniziando con Python, ma provenendo da Javascript il modo più ovvio è estrarre l'espressione come funzione ....

Esempio contrastato, l'espressione di moltiplicazione (x*2)viene estratta come funzione e quindi posso usare multilinea:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Forse non risponde esattamente alla domanda se quello era come fare multilinea nell'espressione lambda stessa , ma nel caso qualcuno ottenga questo thread cercando come eseguire il debug dell'espressione (come me) penso che aiuterà


2
Perché dovrei farlo e non semplicemente scrivere map(multiply, [1, 2, 3])?
thothal

0

A proposito di brutti hack, puoi sempre usare una combinazione di exece una funzione regolare per definire una funzione multilinea come questa:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

Puoi inserirlo in una funzione come:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')

0

Stavo solo giocando un po 'per cercare di capire meglio con riduci, e ho escogitato un unico trucco:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

Stavo solo cercando di fare lo stesso di quello che è stato fatto in questa comprensione del dict Javascript: https://stackoverflow.com/a/11068265


0

Ecco un'implementazione più interessante di lambda multi linea. Non è possibile ottenere a causa del modo in cui Python usa i rientri come modo per strutturare il codice.

Fortunatamente per noi, la formattazione del rientro può essere disabilitata usando array e parentesi.

Come alcuni hanno già sottolineato, puoi scrivere il tuo codice come tale:

lambda args: (expr1, expr2,... exprN)

In teoria, se hai la garanzia di avere una valutazione da sinistra a destra, funzionerebbe, ma perdi comunque i valori che vengono passati da un'espressione all'altra.

Un modo per ottenere ciò che è un po 'più dettagliato è avere

lambda args: [lambda1, lambda2, ..., lambdaN]

Dove ogni lambda riceve argomenti dalla precedente.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

Questo metodo consente di scrivere qualcosa che è un po 'lisp / schema.

Quindi puoi scrivere cose come questa:

let(lambda x, y: x+y)((1, 2))

Un metodo più complesso potrebbe essere utilizzato per calcolare l'ipotenusa

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

Ciò restituirà un elenco di numeri scalari in modo che possa essere utilizzato per ridurre più valori a uno.

Avere così tante lambda non sarà certamente molto efficiente, ma se sei vincolato può essere un buon modo per fare qualcosa velocemente, quindi riscriverlo come una vera funzione in seguito.


-3

perché si suppone che una funzione lambda sia a riga singola, poiché è la forma più semplice di una funzione, an entrance, then return


1
No, chiunque scriva un programma guidato dagli eventi ti dirà che lambda multilinea è importante.
Akangka,
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.