Perché Python non consente lambda multilinea?


50

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

Questo è buono:

lambda x: x**x

Ciò provoca un errore:

lambda x:
    x**x

Capisco che rendere Lambda multilinea in qualche modo "disturberebbe" le normali regole di rientro e richiederebbe l'aggiunta di ulteriori 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 multilinea solo per passarla come argomento?


3
Considerando che noti i motivi concreti per cui Guido non ammette lambda multi-espressione e poi li elimina, suppongo che stai cercando una convalida piuttosto che una vera risposta.
Jason Baker,

3
Oltre a salvare sette personaggi, come è meglio di un def? Ora ha esattamente la stessa struttura visiva.
detenere l'

Risposte:


42

Guido van van Rossum rispose lui stesso:

Ma tali soluzioni spesso mancano di "Pythonicity", quel tratto sfuggente di una buona funzionalità di Python. È impossibile esprimere la pitonicità come un forte vincolo. Anche lo Zen di Python non si traduce in un semplice test di Pythonicity ...

Nell'esempio sopra, è facile trovare il tallone d'Achille della soluzione proposta: il doppio colon, sebbene effettivamente sintatticamente non ambiguo (uno dei "vincoli del puzzle"), è completamente arbitrario e non assomiglia a nient'altro in Python ...

Ma lo sto anche rifiutando, perché alla fine (ed è qui che ammetto di aver fuorviato involontariamente il mittente) 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.

http://www.artima.com/weblogs/viewpost.jsp?thread=147358

Fondamentalmente, dice che sebbene sia possibile una soluzione, non è congruente con come è Python.


2
+1 grazie per il link del thread - ma apprezzerei ancora i lambda multi-linea - sono inestimabili - guarda JavaScript, PHP li ha inclusi anche.
treecoder,

2
@greengit Puoi semplicemente usare un def nidificato. Non è la stessa delle funzioni anonime, ma sono abbastanza vicine.
jsternberg,

2
i def nidificati non aiutano quando si passano funzioni come argomenti - questa è la ragione numero uno per cui mi piacerebbe lambds multi-linea
treecoder

1
@greengit - Penso che farai meglio a prendere questo con GvR piuttosto che pubblicare i tuoi commenti qui.
Jason Baker,

9
@greengit: sai che puoi passare la funzione come argomento a un'altra funzione? Non puoi scriverlo in linea, ma non esiste una tecnica di programmazione che non è disponibile per te.
btilly

24

va benissimo fare una lambda multilinea in pitone: 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 python2 printo return).

GvR sceglie di farlo per limitare le dimensioni della lambda, poiché normalmente vengono utilizzate come parametri. Se vuoi una vera funzione, usadef


1
più la linea è di circa l'inserto del '\ n' carattere: D python non ha più comunicato lambda. Vuoi davvero usare def. Pensaci: hai davvero bisogno di un callable come parametro della tua funzione? E gli utenti di quella funzione non sono autorizzati a passare il tuo richiamo predefinito? Come possono passarlo se non glielo dai?
Vito De Tullio,

btw, puoi fornire un esempio della tua necessità di una funzione anonima?
Vito De Tullio,

1
Sì, trovo davvero frustrante la limitazione di una singola espressione. È vero, che se consentano lambda multi-espressione le persone inizieranno sicuramente ad abusarne, ma viceversa è un omho troppo restrittivo.
rbaleksandar,

10

So che questo è super vecchio, ma mettendo qui come riferimento.

Un'alternativa all'utilizzo di lambda potrebbe essere quella di utilizzare defuna modalità non convenzionale. L'obiettivo è passare defa una funzione, che può essere eseguita in una sola circostanza: un decoratore. Si noti che con questa implementazione def resultnon si crea una funzione, si crea il risultato di reduce(), che finisce per essere un dict.

Spina senza vergogna : faccio molto 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 lambda multiistruzione può essere fatto, ma solo con un codice davvero brutto. Tuttavia, ciò che è interessante è come funziona l'ambito con questa implementazione (notare l'uso multiplo della namevariabile e l'ombreggiamento della messagevariabile.

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

+1 per un approccio monadico
jozefg

Le monadi sono anche chiamate alloraableable o future / promesse o persino callback in JavaScript BTW.
aoeu256,

3

Hacking insieme un lambda multi-affermazione è non è così male come pyrospade fa fuori: sicuro che potremmo comporre un gruppo di funzioni che utilizzano monadici legano, come in Haskell, ma dal momento che siamo nel mondo impura di Python, tanto vale usa gli effetti collaterali per ottenere la stessa cosa.

Copro alcuni modi per farlo sul mio blog .

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

Quindi i seguenti sono equivalenti:

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]

Nota che ho aggiunto Nonea alla fine e l'ho estratto usando [-1]; questo imposta esplicitamente il valore di ritorno. Non dobbiamo farlo, ma senza di esso otterremmo un (None, None, None)valore di ritorno funky , di cui potremmo o meno preoccuparci.

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

Python's =forma un'affermazione, quindi dobbiamo trovare un'espressione equivalente. Un modo è quello di mutare i contenuti della struttura di dati, passati come argomento. Per 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])({})

Ci sono alcuni trucchi utilizzati in stateful_lambda:

  • L' *_argomento consente alla nostra lambda di accettare qualsiasi numero di argomenti. Poiché ciò consente zero argomenti, ripristiniamo la convenzione di chiamata di stateful_def.
    • Chiamare un argomento _è solo una convenzione che dice "Non ho intenzione di usare questa variabile"
  • Abbiamo una funzione ("wrapper") che restituisce un'altra funzione ("principale"): lambda state: lambda *_: ...
    • Grazie all'ambito lessicale , l'argomento della prima funzione sarà nell'ambito della seconda funzione
    • Accettare alcuni argomenti ora e restituire un'altra funzione per accettare il resto in seguito è noto come curry
  • Chiamiamo immediatamente la funzione "wrapper", passando un dizionario vuoto: (lambda state: ...)({})
    • Questo ci consente di assegnare una variabile statea un valore {}senza utilizzare un'istruzione di assegnazione (ad es. state = {})
  • Trattiamo chiavi e valori statecome nomi di variabili e valori associati
    • Questo è meno ingombrante rispetto all'utilizzo di lambda immediatamente chiamati
    • Questo ci consente di mutare i valori delle variabili
    • Usiamo state.setdefault(a, b)invece di a = be state.get(a)invece dia
  • Usiamo una tupla per concatenare i nostri effetti collaterali, come prima
  • Usiamo [-1]per estrarre l'ultimo valore, che si comporta come returnun'istruzione

Naturalmente questo è piuttosto ingombrante, ma possiamo creare un'API più bella con 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])

stai scherzando, sembra un incubo lol
Alexander Mills,

1
@AlexanderMills Heh, questo non era inteso come un esempio del mondo reale, più di una confutazione dell'approccio lambdas-in-lambdas-in-lambdas di pyrospade, per dimostrare che le cose non sono poi così male. In effetti, questo potrebbe essere semplificato molto di più ora che abbiamo python.org/dev/peps/pep-0572
Warbo

1

Ho pensato di poter contribuire, utilizzare 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 oneliners, come nell'esempio:

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

E la lambda è molto potente, ma non è pensata per la sostituzione di 1 intera funzione, intendo che potresti hackerarla come (prendendo in 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? È per lo più illeggibile dopo un po 'di tempo, è come arrivare all'inizio della corda iniziando con una fine non spiegata. corda non dipinta

Le lambda sono menzionate come una sola funzione, nella mappa, filtrano e riducono le funzioni nella programmazione orientata al funzionamento (tra le altre cose). Ad esempio, ottenere valori di caratteri di valori che sono numeri interi e divisibili per 2

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = 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]

Puoi usarlo come funzione expresions deifigendo una funzione complessa (o più e più lambda e inserendola in un'altra lambda:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

ma Python ha il supporto delle espressioni di funzione in un altro modo: -let dicono che hai una funzione chiamata superAwesomeFunctione che la funzione può fare cose super fantastiche, puoi assegnarla a una variabile non chiamandola, in questo modo:

SAF = superAwesomeFunction # there is no () at the end, 

Quindi ora quando chiami SAF chiamerai superAwesomeFunction o metodo. Se cerchi attraverso la tua cartella Lib puoi scoprire che la maggior parte dei __builtin__moduli Python sono scritti in questo modo. Questo viene fatto perché a volte sono necessarie alcune funzioni che svolgono attività specifiche che non sono abbastanza necessarie per essere utilizzabili dall'utente ma è necessario per diverse funzioni. Quindi hai una scelta che non puoi avere 2 funzioni con il nome "superAwesomeFunction", puoi avere "superAwesomeFunctionDoingBasicStuf" e "realSuperAwesomeFunction" e non solo inserire la "realSuperAwesomeFunction" nella variabile "superAwesomeFunction" e il gioco è fatto.

Puoi trovare la posizione dei moduli importati entrando nella console importedModule.__file__(esempio reale import os;os.__file__), e basta seguire quella directory per il file chiamato ImportModule.py e aprirlo nell'editor e scoprire come massimizzare la tua "conoscenza".

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

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.