Perché Python non consente i lambda multi-linea?

44

Qualcuno può spiegare i motivi concreti per cui BDFL ha scelto di creare una linea singola di lambdas Python?

Questo è buono:

lambda x: x**x

Questo genera un errore:

lambda x:
    x**x

Capisco che fare lambda multi-line in qualche modo "disturba" le normali regole di indentazione e richiederebbe più eccezioni, ma non ne vale la pena?

Guarda JavaScript, per esempio. Come si può vivere senza quelle funzioni anonime? Sono indispensabili. I pitonisti non vogliono sbarazzarsi di dover nominare ogni funzione multi-riga solo per passarla come argomento?

    
posta treecoder 07.08.2011 - 19:30
fonte

5 risposte

42

Guido van van Rossum ha risposto lui stesso:

But such solutions often lack "Pythonicity" -- that elusive trait of a good Python feature. It's impossible to express Pythonicity as a hard constraint. Even the Zen of Python doesn't translate into a simple test of Pythonicity...

In the example above, it's easy to find the Achilles heel of the proposed solution: the double colon, while indeed syntactically unambiguous (one of the "puzzle constraints"), is completely arbitrary and doesn't resemble anything else in Python...

But I'm rejecting that too, because in the end (and this is where I admit to unintentionally misleading the submitter) I find any solution unacceptable that embeds an indentation-based block in the middle of an expression. Since I find alternative syntax for statement grouping (e.g. braces or begin/end keywords) equally unacceptable, this pretty much makes a multi-line lambda an unsolvable puzzle.

link

Fondamentalmente, dice che sebbene una soluzione sia possibile, non è congruente con il modo in cui Python è.

    
risposta data 07.08.2011 - 19:39
fonte
22

è perfettamente adatto per fare un lambda multi linea in python: vedi

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

la vera limitazione lambda è il fatto che lambda deve essere una singola espressione ; non può contenere parole chiave (come %%% di %% o%% di% di python2.

GvR ha scelto di farlo per limitare la dimensione del lambda, dato che normalmente vengono usati come parametri. Se vuoi una funzione reale, usa print

    
risposta data 07.08.2011 - 20:03
fonte
8

So che è super vecchio, ma che metto qui come riferimento.

Un'alternativa all'utilizzo di lambda potrebbe essere quella di usare un def in modo non convenzionale. L'obiettivo è passare un def a una funzione, che può essere eseguita in una sola circostanza, un decoratore. Nota che con questa implementazione def result non crea una funzione, crea il risultato di reduce() , che finisce per essere un dict .

Plug spudorato : faccio un sacco di questo qui .

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

Nota che è possibile eseguire can di istruzioni multiple, ma solo con codice veramente brutto. Tuttavia, ciò che è interessante è il modo in cui lo scoping funziona con questa implementazione (si noti l'utilizzo multiplo della variabile name e l'ombreggiamento della variabile message .

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!
    
risposta data 16.11.2013 - 02:57
fonte
3

L'hacking di un lambda a più istruzioni non è così grave come il pyrospade: sicuramente potremmo comporre un gruppo di funzioni monadiche usando bind, come in Haskell, ma visto che siamo in il mondo impuro di Python, potremmo anche usare gli effetti collaterali per ottenere la stessa cosa.

Ho spiegato alcuni modi per farlo in mio blog .

Ad esempio, Python garantisce di valutare gli elementi di una tupla in ordine, quindi possiamo usare , in modo molto simile a un imperativo ; . Possiamo sostituire molte affermazioni, come print , con espressioni, come sys.stdout.write .

Quindi quanto segue è equivalente:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

Tieni presente che ho aggiunto un None alla fine e l'ho estratto con [-1] ; questo imposta il valore di ritorno in modo esplicito. Non dobbiamo farlo, ma senza di esso otterremmo un valore di ritorno funky (None, None, None) , che potremmo o non potremmo importare.

Quindi possiamo sequenziare le azioni IO. Che dire delle variabili locali?

Il% co_de di Python forma un'istruzione, quindi dobbiamo trovare un'espressione equivalente. Un modo è quello di mutare il contenuto della struttura dati, passato come argomento. Ad esempio:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

Sono disponibili pochi trucchi in = :

  • L'argomento stateful_lambda consente al nostro lambda di prendere qualsiasi numero di argomenti. Poiché ciò consente argomenti zero , recuperiamo la convenzione di chiamata di *_ .
    • Chiamare un argomento stateful_def è solo una convenzione che dice "Non userò questa variabile"
  • Abbiamo una funzione ("wrapper") che restituisce un'altra funzione ("principale"): _
    • Grazie a ambito lessicale , l'argomento della prima funzione sarà in-scope per il seconda funzione
    • Accettare ora alcuni argomenti e restituire un'altra funzione per accettare il resto in seguito è noto come currying
  • Chiamiamo immediatamente la funzione "wrapper", passandole un dizionario vuoto: lambda state: lambda *_: ...
    • Questo ci consente di assegnare una variabile (lambda state: ...)({}) a un valore state senza utilizzare un'istruzione di assegnazione (ad esempio {} )
  • Trattiamo chiavi e valori in state = {} come nomi di variabili e valori associati
    • Questo è meno ingombrante dell'uso di lambda immediatamente
    • Questo ci consente di modificare i valori delle variabili
    • Utilizziamo state anziché state.setdefault(a, b) e a = b anziché state.get(a)
  • Usiamo una tupla per mettere insieme i nostri effetti collaterali, come prima
  • Usiamo a per estrarre l'ultimo valore, che si comporta come un'istruzione [-1]

Ovviamente questo è piuttosto ingombrante, ma possiamo creare un'API migliore con le funzioni di supporto:

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the 'get' and 'setdefault' methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh 'get' and 'setdefault'
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses 'get' and 'set'(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])
    
risposta data 06.08.2014 - 22:38
fonte
1

Anche se potrei contribuire, usa un interruttore di linea:

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

Non dimenticare la cosa molto bella che python è in grado di scrivere su oneliner, come nell'esempio:

a=b=0; c=b+a; d = a+b**2 #etc etc

E lambda è molto potente, ma non è inteso per la sostituzione di 1 intera funzione, voglio dire che potresti hackerarlo come (prendendo a prestito un esempio dal collega sopra):

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

Ma vuoi davvero farlo in questo modo? È in gran parte illeggibile dopo un po 'di tempo, è come arrivare all'inizio della corda che inizia con la fine disfatta.

Lambdasfungedaunicafunzione,mappa,filtroeriduzionedellefunzioninellaprogrammazioneorientataalfunzionamento(tralealtrecose).Adesempioottenendovaloridicaratteredivalorichesonointeriedivisibiliper2

chrDev2=lambdaINT:chr(INT)ifisinstance(INT,int)andINT%2==0elseINTsomeStringList=map(chrDev2,range(30))>>>['\x00',1,'\x02',3,'\x04',5,'\x06',7,'\x08',9,'\n',11,'\x0c',13,'\x0e',15,'\x10',17,'\x12',19,'\x14',21,'\x16',23,'\x18',25,'\x1a',27,'\x1c',29]

Potrestiusarlocomefunzionediespressionidifunzionidefinendolafunzionecomplessa(opiùepiùlambdaeinserendolainunaltrolambda:

defsomeAnon(*args):returnsum(list(args))defAnon=lambdalist:[x*someAnon(*list)forxinlist]

maPythonhailsupportodelleespressionidifunzioneinunaltromodo:-letdiconochehaiunafunzionechiamatasuperAwesomeFunctionequellafunzionepuòfarecosesuper-fantastiche,puoiassegnarlaaunavariabilenonchiamandola,comequesta:

SAF=superAwesomeFunction#thereisno()attheend,

QuindioraquandochiamiSAFchiameraisuperAwesomeFunctionometodo.SecerchiattraversolatuacartellaLibpuoitrovarechelamaggiorpartedeimodulipython__builtin__sonoscrittiinquestomodo.Ciòèpossibileperchéavoltesononecessariealcunefunzionicheeseguonoun'attivitàspecificachenonènecessariaabbastanzaperessereutilizzabiledall'utente,maènecessariaperdiversefunzioni.Quindihaiunasceltachenonpuoiavere2funzioniconilnome"superAwesomeFunction", puoi avere "superAwesomeFunctionDoingBasicStuf" e "realSuperAwesomeFunction" e poi mettere la "realSuperAwesomeFunction" nella variabile "superAwesomeFunction" e il gioco è fatto.

Puoi trovare la posizione dei moduli importati inserendo nella console importedModule.__file__ (esempio reale import os;os.__file__ ) e segui quella directory nel file chiamato importedModule.py e aprilo nell'editor e scopri come puoi massimizzare la tua "conoscenza".

Spero che questo aiuti voi e forse altri colleghi nei guai.

    
risposta data 23.12.2016 - 20:52
fonte

Leggi altre domande sui tag