Kein mehrzeiliges Lambda in Python: Warum nicht?

334

Ich habe gehört, dass mehrzeilige Lambdas in Python nicht hinzugefügt werden können, da sie syntaktisch mit den anderen Syntaxkonstrukten in Python kollidieren würden. Ich habe heute im Bus darüber nachgedacht und festgestellt, dass ich mir kein einziges Python-Konstrukt vorstellen kann, mit dem mehrzeilige Lambdas in Konflikt geraten. Da ich die Sprache ziemlich gut kenne, hat mich das überrascht.

Ich bin mir sicher, dass Guido einen Grund hatte, mehrzeilige Lambdas nicht in die Sprache aufzunehmen, aber aus Neugier: In welcher Situation wäre die Aufnahme eines mehrzeiligen Lambdas mehrdeutig? Ist das, was ich gehört habe, wahr oder gibt es einen anderen Grund, warum Python keine mehrzeiligen Lambdas zulässt?

Imagist
quelle
12
tl; dr version: weil Python eine faule Sprache ohne {} Blöcke ist und dies daher nicht erlaubt war, um ein konsistentes syntaktisches Design beizubehalten.
Andrew
11
Außerdem: Ich bin zutiefst überrascht, dass dies in den Antworten niemand erwähnt hat ... Sie können Zeilen mit dem Zeichen \ in Python beenden und mit der nächsten Zeile fortfahren ... Diese Informationen ersetzen diese ganze Frage irgendwie ...
Andrew
"syntaktisches Design"
Nicolas
Dies würde das Zulassen von Anweisungen in Ausdrücken erfordern. Wenn Sie das tun wollen, brauchen Sie überhaupt keine lambdaAusdrücke. Sie können einfach defAnweisungen in Ausdrücken verwenden.
Chepper

Antworten:

153

Schauen Sie sich Folgendes an:

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

Ist dies ein Lambda, das zurückkehrt (y, [1,2,3])(daher erhält map nur einen Parameter, was zu einem Fehler führt)? Oder kehrt es zurück y? Oder handelt es sich um einen Syntaxfehler, weil das Komma in der neuen Zeile falsch platziert ist? Wie würde Python wissen, was Sie wollen?

Innerhalb der Parens spielt die Einrückung für Python keine Rolle, sodass Sie nicht eindeutig mit Multilines arbeiten können.

Dies ist nur eine einfache, es gibt wahrscheinlich mehr Beispiele.

balpha
quelle
106
Sie können die Verwendung von Klammern erzwingen, wenn Sie ein Tupel von einem Lambda zurückgeben möchten. IMO, dies hätte immer durchgesetzt werden müssen, um solche Unklarheiten zu vermeiden, aber na ja.
Mpen
26
Dies ist eine einfache Mehrdeutigkeit, die gelöst werden muss, indem ein zusätzlicher Satz von Parens hinzugefügt wird, der bereits an vielen Stellen vorhanden ist, z. B. Generatorausdrücke, die von anderen Argumenten umgeben sind und eine Methode für ein ganzzahliges Literal aufrufen (obwohl dies seit a nicht der Fall sein muss Funktionsname kann nicht mit einer Ziffer beginnen) und natürlich auch einzeilige Lambdas (dies können lange Ausdrücke sein, die in mehreren Zeilen geschrieben sind). Mehrzeilige Lambdas würden sich nicht besonders von diesen Fällen unterscheiden, die es rechtfertigen, sie auf dieser Grundlage auszuschließen. Das ist die wahre Antwort.
nmclean
3
Ich mag, wie es Millionen Sprachen gibt, die damit umgehen, ohne sich zu ärgern, aber irgendwie gibt es einige tiefe Gründe, warum es angeblich sehr schwer, wenn nicht unmöglich ist
Nicole
1
@nicolas das ist Python auf den Punkt gebracht
javadba
Grund, warum ich kein Lambda verwende, das in Python so unterentwickelt ist.
NoName
634

Guido van Rossum (der Erfinder von Python) beantwortet genau diese Frage selbst in einem alten Blog-Beitrag .
Grundsätzlich gibt er zu, dass es theoretisch möglich ist, aber dass jede vorgeschlagene Lösung nicht pythonisch wäre:

"Aber die Komplexität jeder vorgeschlagenen Lösung für dieses Rätsel ist für mich immens: Der Parser (oder genauer gesagt der Lexer) muss in der Lage sein, zwischen indent-sensitiven und indent-unempfindlichen Modi hin und her zu wechseln und einen Stapel zu behalten Technisch gesehen kann das alles gelöst werden (es gibt bereits einen Stapel von Einrückungsstufen, die verallgemeinert werden könnten). Aber nichts davon nimmt mir das Gefühl, dass es sich um eine ausgefeilte Rube Goldberg-Erfindung handelt . "

Eli Courtwright
quelle
108
Warum ist das nicht die beste Antwort? Es geht nicht um die technischen Gründe, es ist eine Designentscheidung, wie vom Erfinder klar angegeben.
Dan Abramov
13
@ DanAbramov, weil sich das OP wahrscheinlich jahrelang nicht angemeldet hat.
Prof. Falken Vertrag verletzt
7
Für diejenigen, die die Rube Goldberg Referenz nicht verstanden haben, siehe: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj
56
Guidos Antwort ist nur ein weiterer Grund, warum ich mir wünschte, Python wäre nicht auf Einrückungen angewiesen, um Blöcke zu definieren.
LS
25
Ich bin mir nicht sicher, ob ich "Bauchgefühl" als Designentscheidung bezeichnen würde. ;)
Elliot Cameron
54

Dies ist im Allgemeinen sehr hässlich (aber manchmal sind die Alternativen sogar noch hässlicher). Eine Problemumgehung besteht darin, einen Ausdruck in geschweiften Klammern zu erstellen:

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

Es werden jedoch keine Zuweisungen akzeptiert, sodass Sie die Daten im Voraus vorbereiten müssen. Der Ort, an dem ich dies nützlich fand, ist der PySide-Wrapper, bei dem manchmal kurze Rückrufe auftreten. Das Schreiben zusätzlicher Mitgliedsfunktionen wäre noch hässlicher. Normalerweise brauchen Sie das nicht.

Beispiel:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
Sebastian Bartos
quelle
2
Mein Chef hat gerade in unserer PyQt-Anwendung nach so etwas gefragt. Genial!
TheGerm
1
Vielen Dank dafür, ich suchte auch nach einer guten Möglichkeit, kurze (aber immer noch mehrzeilige) Lambdas als Rückruf für unsere PySide-Benutzeroberfläche zu verwenden.
Michael Leonard
Und jetzt habe ich gesehen, dass es sofort vorgeschlagen hat , "keine Zuweisungen ..." zu verwenden lambda argund setattr(arg, 'attr','value')zu untergraben. Und dann gibt es eine Kurzschlussauswertung von andund or... es ist das Javascript, das das macht. Versenkt Wurzeln in dir, wie Efeu in eine Wand. Ich hoffe fast, dass ich das über Weihnachten vergesse.
Nigel222
ziemlich schlau - und gut lesbar. Nun - über diese (fehlenden ..) Aufgaben ..
Javadba
@ nigel222 warum schämen? Die Python-Sprache ist grundlegend verkrüppelt - aber sie wird sowieso für einen Großteil der Datenwissenschaft verwendet . Also nehmen wir Anpassungen vor. Das Finden von Möglichkeiten, um Nebenwirkungen (oft genug einfach drucken / protokollieren!) Und Aufgaben (oft genug nur für Zwischenvarianten!) Zu finden, sollte von der Sprache gut gehandhabt werden. Aber sie werden nicht einmal unterstützt (zumindest wenn Sie PEPRichtlinien befolgen )
javadba
17

Einige relevante Links:

Eine Weile verfolgte ich die Entwicklung von Reia, das ursprünglich Pythons einrückungsbasierte Syntax mit Ruby-Blöcken auf Erlang enthalten sollte. Der Designer gab jedoch die Einrückempfindlichkeit auf, und dieser Beitrag, den er über diese Entscheidung schrieb, enthielt eine Diskussion über Probleme, auf die er mit Einrückungen + mehrzeiligen Blöcken stieß, und eine erhöhte Wertschätzung, die er für Guidos Designprobleme / -entscheidungen erhielt:

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

Hier ist auch ein interessanter Vorschlag für Ruby-ähnliche Blöcke in Python, auf die Guido gestoßen ist, wo Guido eine Antwort veröffentlicht, ohne sie tatsächlich abzuschießen (ich bin mir jedoch nicht sicher, ob es einen nachfolgenden Abschuss gegeben hat):

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

Anon
quelle
12

[Bearbeiten] Lesen Sie diese Antwort. Es erklärt, warum mehrzeiliges Lambda keine Sache ist.

Einfach gesagt, es ist unpythonisch. Aus Guido van Rossums Blogbeitrag:

Ich finde jede Lösung inakzeptabel, die einen einrückungsbasierten Block in die Mitte eines Ausdrucks einbettet. Da ich alternative Syntax für die Gruppierung von Anweisungen (z. B. geschweifte Klammern oder Schlüsselwörter für Anfang / Ende) ebenso inakzeptabel finde, macht dies ein mehrzeiliges Lambda zu einem unlösbaren Rätsel.

Samy Bencherif
quelle
10

Lassen Sie mich Ihnen einen herrlichen, aber schrecklichen Hack präsentieren:

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)

Sie können dieses LETFormular jetzt als solches verwenden:

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

was gibt: [0, 3, 8]

divs1210
quelle
1
Ursprünglich geschrieben unter gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210
Das ist fantastisch! Ich denke, ich werde das nächste Mal verwenden, wenn ich Python schreibe. Ich bin in erster Linie ein Lisp- und JS-Programmierer, und das Fehlen von mehrzeiligem Lambada tut weh. Dies ist ein Weg, um das zu bekommen.
Christopher Dumas
7

Ich bin schuldig, diesen schmutzigen Hack in einigen meiner Projekte geübt zu haben, was etwas einfacher ist:

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

Ich hoffe, Sie können einen Weg finden, um pythonisch zu bleiben, aber wenn Sie dies tun müssen, ist dies weniger schmerzhaft als die Verwendung von exec und die Manipulation von Globals.

S.Rad
quelle
6

Lassen Sie mich versuchen, das @ balpha-Parsing-Problem anzugehen. Ich würde Klammern um die mehrzeilige Lamda verwenden. Wenn es keine Klammern gibt, ist die Lambda-Definition gierig. Also das Lambda rein

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

Gibt eine Funktion zurück, die zurückgibt (y*z, [1,2,3])

Aber

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

meint

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

Dabei ist func das mehrzeilige Lambda, das y * z zurückgibt. Funktioniert es?

Wai Yip Tung
quelle
1
Ich denke, der obere sollte zurückkehren map(func, [1,2,3])und der untere sollte ein Fehler sein, weil die Map-Funktion nicht genügend Argumente hat. Außerdem enthält der Code einige zusätzliche Klammern.
Samy Bencherif
Wenn Sie dies in pycharm ablegen, auf dem python2.7.13 ausgeführt wird, wird ein Syntaxfehler ausgegeben.
simbo1905
zusätzliche Klammern
Samy Bencherif
4

(Für alle, die sich noch für das Thema interessieren.)

Bedenken Sie dies (schließt sogar die Verwendung der Rückgabewerte von Anweisungen in weiteren Anweisungen innerhalb des "mehrzeiligen" Lambda ein, obwohl es bis zum Erbrechen hässlich ist ;-)

>>> 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
vencik
quelle
Dies funktioniert nicht, wenn es ein zweites Mal mit unterschiedlichen Parametern aufgerufen wird, und verursacht einen Speicherverlust, es sei denn, die erste Zeile ist, state.clear()da Standardargumente beim Erstellen der Funktion nur einmal erstellt werden.
Matthew D. Scholefield
1

Sie können einfach slash ( \) verwenden, wenn Sie mehrere Zeilen für Ihre Lambda-Funktion haben

Beispiel:

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

Output: 30
IRSHAD
quelle
Die Frage betrifft die Verwendung von mehr als einem Ausdruck anstelle von mehr als einer wörtlichen Zeile.
Tomas Zubiri
1

Ich beginne mit Python, aber wenn ich aus Javascript komme, ist es am offensichtlichsten, den Ausdruck als Funktion zu extrahieren.

Erfundenes Beispiel, Multiplikationsausdruck (x*2)wird als Funktion extrahiert und daher kann ich Multiline verwenden:

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

Vielleicht beantwortet es nicht genau die Frage, ob dies die Mehrzeiligkeit des Lambda-Ausdrucks selbst ist , aber falls jemand diesen Thread erhält, der nach dem Debuggen des Ausdrucks sucht (wie ich), denke ich, dass es helfen wird

Vicens Fayos
quelle
2
Warum sollte ich das tun und nicht einfach schreiben map(multiply, [1, 2, 3])?
Thothal
0

Bei hässlichen Hacks können Sie immer eine Kombination aus execund eine reguläre Funktion verwenden, um eine mehrzeilige Funktion wie die folgende zu definieren:

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

Sie können dies in eine Funktion wie die folgende einbinden:

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')
Matthew D. Scholefield
quelle
0

Ich habe nur ein bisschen gespielt, um zu versuchen, ein Diktatverständnis mit Reduzieren zu machen, und mir diesen One-Liner-Hack ausgedacht:

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}

Ich habe nur versucht, dasselbe zu tun wie in diesem Javascript-Diktat: https://stackoverflow.com/a/11068265

rodfersou
quelle
0

Hier ist eine interessantere Implementierung von mehrzeiligen Lambdas. Dies ist nicht möglich, da Python Einrückungen zum Strukturieren von Code verwendet.

Glücklicherweise kann die Formatierung von Einzügen mithilfe von Arrays und Klammern deaktiviert werden.

Wie einige bereits betont haben, können Sie Ihren Code als solchen schreiben:

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

Theoretisch würde es funktionieren, wenn Sie garantiert eine Bewertung von links nach rechts haben, aber Sie verlieren immer noch Werte, die von einem Ausdruck an einen anderen übergeben werden.

Ein Weg, um das zu erreichen, was etwas ausführlicher ist, ist zu haben

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

Wobei jedes Lambda Argumente vom vorherigen erhält.

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

Mit dieser Methode können Sie etwas schreiben, das ein bisschen lisp / Schema ist.

Sie können also Folgendes schreiben:

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

Eine komplexere Methode könnte verwendet werden, um die Hypotenuse zu berechnen

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

Dadurch wird eine Liste mit skalaren Zahlen zurückgegeben, sodass mehrere Werte auf einen reduziert werden können.

So viele Lambda zu haben, wird sicherlich nicht sehr effizient sein, aber wenn Sie eingeschränkt sind, kann dies eine gute Möglichkeit sein, etwas schnell zu erledigen und es später als tatsächliche Funktion neu zu schreiben.

Loïc Faure-Lacroix
quelle
-3

weil eine Lambda-Funktion einzeilig sein soll, da sie die einfachste Form einer Funktion ist, an entrance, then return

Sphynx-HenryAY
quelle
1
Nein, jeder, der ein ereignisgesteuertes Programm schreibt, wird Ihnen sagen, dass mehrzeiliges Lambda wichtig ist.
Akangka