Warum erlaubt Python keine mehrzeiligen Lambdas?

50

Kann jemand die konkreten Gründe erklären, warum BDFL Python Lambdas als Einzellinie verwendet?

Das ist gut:

lambda x: x**x

Dies führt zu einem Fehler:

lambda x:
    x**x

Ich verstehe, dass das Herstellen von Lambda-Mehrfachzeilen die normalen Einrückungsregeln irgendwie "stören" und das Hinzufügen weiterer Ausnahmen erfordern würde, aber ist das nicht die Vorteile wert?

Schauen Sie sich zum Beispiel JavaScript an. Wie kann man ohne diese anonymen Funktionen leben? Sie sind unverzichtbar. Wollen Pythonisten nicht loswerden, dass sie jede mehrzeilige Funktion benennen müssen, nur um sie als Argument zu übergeben?

Baumkodierer
quelle
3
In Anbetracht der Tatsache, dass Sie die konkreten Gründe notieren, warum Guido Lambdas mit mehreren Ausdrücken nicht zulässt, und sie dann ablehnen, gehe ich davon aus, dass Sie eine Validierung und keine echte Antwort suchen.
Jason Baker
3
Wie ist das besser als ein, außer dass sieben Zeichen gespeichert werden def? Es hat jetzt genau die gleiche visuelle Struktur.
Detly

Antworten:

42

Guido van van Rossum selbst antwortete:

Aber solchen Lösungen fehlt oft "Pythonicity" - das schwer fassbare Merkmal einer guten Python-Funktion. Es ist unmöglich, Pythonicity als eine harte Einschränkung auszudrücken. Selbst das Zen von Python ist kein einfacher Test für Pythonicity ...

Im obigen Beispiel ist die Achillesferse der vorgeschlagenen Lösung leicht zu finden: Der doppelte Doppelpunkt ist zwar syntaktisch eindeutig (eine der "Rätseleinschränkungen"), aber völlig willkürlich und ähnelt nichts anderem in Python ...

Aber ich lehne das auch ab, weil ich am Ende (und das ist der Punkt, an dem ich zugebe, dass ich den Übermittler unbeabsichtigt irregeführt habe) eine inakzeptable Lösung finde, die einen einrückungsbasierten Block in die Mitte eines Ausdrucks einbettet. Da ich eine alternative Syntax für die Gruppierung von Anweisungen (z. B. geschweifte Klammern oder Start- / End-Schlüsselwörter) ebenfalls nicht akzeptabel finde, macht dies ein mehrzeiliges Lambda zu einem unlösbaren Rätsel.

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

Grundsätzlich sagt er, dass eine Lösung zwar möglich ist, aber nicht mit Python übereinstimmt.

BlackJack
quelle
2
+1 danke für den Thread-Link - aber ich würde mich immer noch über mehrzeilige Lambdas freuen - sie sind von unschätzbarem Wert - sieh dir JavaScript an, PHP hat sie auch enthalten.
Treecoder
2
@greengit Du kannst einfach ein verschachteltes def verwenden. Es ist nicht dasselbe wie bei anonymen Funktionen, aber sie sind nah genug.
Sternberg
2
Verschachtelte Defs helfen nicht, wenn Funktionen als Argumente übergeben werden - das ist der Hauptgrund, warum ich mehrzeilige Lambds haben möchte
Treecoder
1
@greengit - Ich denke, Sie sollten dies besser mit GvR aufnehmen, als Ihre Kommentare hier zu veröffentlichen.
Jason Baker
9
@greengit: Weißt du, dass du die Funktion als Argument an eine andere Funktion übergeben kannst? Sie können es nicht inline schreiben, aber es gibt keine Programmiertechnik, die Ihnen nicht zur Verfügung steht.
btilly
24

Es ist vollkommen in Ordnung, ein mehrzeiliges Lambda in Python zu machen: siehe

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

Die wirkliche Lambda-Beschränkung ist die Tatsache, dass Lambda ein einzelner Ausdruck sein muss . es darf kein Schlüsselwort enthalten (wie python2 printoder return).

GvR wählt dies aus, um die Größe des Lambdas zu begrenzen, da diese normalerweise als Parameter verwendet werden. Wenn Sie eine echte Funktion wünschen, verwenden Siedef

Vito De Tullio
quelle
1
Bei multi line handelt es sich um die Einfügung des Zeichens '\ n': D python hat keine multi statement lambda. Sie wollen wirklich verwenden def. Denken Sie darüber nach: Sie brauchen wirklich eine aufrufbare als Parameter Ihrer Funktion? Und die Benutzer dieser Funktion dürfen Ihr Standard-Callable nicht weitergeben? Wie können sie es bestehen, wenn Sie es ihnen nicht geben?
Vito De Tullio
Übrigens, können Sie ein Beispiel dafür geben, dass Sie eine anonyme Funktion benötigen?
Vito De Tullio
1
Ja, ich finde die Einschränkung eines einzelnen Ausdrucks wirklich frustrierend. Es ist wahr, dass, wenn sie Lambdas mit mehreren Ausdrücken erlauben, die Menschen mit Sicherheit anfangen werden, sie zu missbrauchen, aber umgekehrt ist es zu restriktiv, oho.
rbaleksandar
10

Ich weiß, das ist super alt, aber hier als Referenz setzen.

Eine Alternative zur Verwendung von Lambda könnte darin bestehen, a defauf nicht herkömmliche Weise zu verwenden. Das Ziel ist es, eine defan eine Funktion zu übergeben, was nur unter einem Umstand möglich ist - einem Dekorateur. Beachten Sie, dass mit dieser Implementierung def resultkeine Funktion erstellt wird, sondern das Ergebnis von erstellt wird reduce(), das letztendlich a ist dict.

Shameless Stecker : Ich mache eine Menge von diesem hier .

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

Beachten Sie, dass Lambdas mit mehreren Anweisungen ausgeführt werden können , jedoch nur mit wirklich, wirklich hässlichem Code. Interessant ist jedoch, wie das Scoping bei dieser Implementierung funktioniert (beachten Sie die Mehrfachverwendung der nameVariablen und das Shadowing der messageVariablen).

>>> 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!
Pyrospade
quelle
+1 für eine monadische Annäherung
jozefg
Monaden werden in JavaScript BTW auch als thenables oder future / promises oder sogar callbacks bezeichnet.
aoeu256
3

Das Zusammenhacken eines Lambda mit mehreren Anweisungen ist nicht ganz so schlimm, wie es Pyrospade vermuten lässt: Sicher könnten wir eine Reihe monadischer Funktionen mit bind komponieren, wie in Haskell, aber da wir uns in der unreinen Welt von Python befinden, könnten wir es auch Verwenden Sie Nebenwirkungen, um das Gleiche zu erreichen.

In meinem Blog beschreibe ich einige Möglichkeiten, dies zu tun .

Zum Beispiel garantiert Python, dass die Elemente eines Tupels in der richtigen Reihenfolge ausgewertet werden, sodass wir sie ,wie einen Imperativ verwenden können ;. Wir können viele Aussagen printdurch Ausdrücke wie ersetzen sys.stdout.write.

Daher sind die folgenden äquivalent:

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]

Beachten Sie, dass ich Noneam Ende ein hinzugefügt und es mit extrahiert habe [-1]. Dadurch wird der Rückgabewert explizit festgelegt. Wir müssen dies nicht tun, aber ohne es würden wir einen flippigen (None, None, None)Rückgabewert erhalten, der uns vielleicht interessiert oder nicht.

So können wir IO-Aktionen abfolgen. Was ist mit lokalen Variablen?

Pythons =bildet eine Aussage, also müssen wir einen äquivalenten Ausdruck finden. Eine Möglichkeit besteht darin, den Inhalt der als Argument übergebenen Datenstruktur zu mutieren. Zum Beispiel:

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])({})

Es gibt einige Tricks, die verwendet werden stateful_lambda:

  • Das *_Argument erlaubt unserem Lambda eine beliebige Anzahl von Argumenten. Da dies keine Argumente zulässt , stellen wir die Aufrufkonvention von wieder her stateful_def.
    • Das Aufrufen eines Arguments _ist nur eine Konvention, die besagt, dass ich diese Variable nicht verwenden werde.
  • Wir haben eine ("wrapper") Funktion, die eine andere ("main") Funktion zurückgibt: lambda state: lambda *_: ...
    • Dank des lexikalischen Gültigkeitsbereichs ist das Argument der ersten Funktion für die zweite Funktion im Gültigkeitsbereich
    • Das Akzeptieren einiger Argumente und das Zurückgeben einer anderen Funktion, um den Rest später zu akzeptieren, wird als Currying bezeichnet
  • Wir rufen sofort die "Wrapper" -Funktion auf und übergeben ihr ein leeres Wörterbuch: (lambda state: ...)({})
    • Auf diese Weise können wir stateeinem Wert eine Variable zuweisen , {}ohne eine Zuweisungsanweisung zu verwenden (z. B. state = {}).
  • Wir behandeln Schlüssel und Werte stateals Variablennamen und gebundene Werte
    • Dies ist weniger umständlich als die Verwendung von sog. Lambdas
    • Dies ermöglicht es uns, die Werte von Variablen zu mutieren
    • Wir verwenden state.setdefault(a, b)anstelle von a = bund state.get(a)anstelle vona
  • Wir verwenden ein Tupel, um unsere Nebenwirkungen wie zuvor zu verketten
  • Wir verwenden [-1], um den letzten Wert zu extrahieren, der sich wie eine returnAnweisung verhält

Das ist natürlich ziemlich umständlich, aber wir können eine schönere API mit Hilfsfunktionen erstellen:

# 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])
Warbo
quelle
Machst du Witze, das sieht aus wie ein Alptraum lol
Alexander Mills
1
@AlexanderMills Heh, dies war nicht als reales Beispiel gedacht, sondern eher als Widerlegung des Lambdas-in-Lambdas-in-Lambdas-Ansatzes von Pyrospade, um zu zeigen, dass die Dinge nicht so schlimm sind. Tatsächlich könnte dies jetzt, da wir python.org/dev/peps/pep-0572
Warbo,
1

Ich könnte zwar einen Beitrag leisten, benutze aber einen Zeilenumbruch:

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

Vergessen Sie nicht, wie schön es ist, dass Python Oneliners schreiben kann, wie im Beispiel:

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

Und das Lambda ist sehr mächtig, aber es ist nicht für die Ersetzung einer ganzen Funktion gedacht, ich meine, Sie könnten es wie folgt hacken (Beispiel von einem Kollegen oben):

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)

Aber willst du es wirklich so machen? Es ist meistens nach einiger Zeit unlesbar, es ist, als würde man mit dem nicht gelösten Ende am Anfang des Seils beginnen. entwirrtes Seil

Lambdas werden unter anderem als Einzelfunktionen in Map-, Filter- und Reduktionsfunktionen in Functional Oriented Programing verwendet. Beispiel: Zeichenwerte von Werten abrufen, die ganzzahlig und durch 2 teilbar sind

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]

Sie können es als Funktion verwenden, indem Sie eine komplexe Funktion (oder mehr und mehrere Lambdas) definieren und in ein anderes Lambda einfügen:

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

Aber Python unterstützt Funktionsausdrücke auf andere Weise: -lets sagen, dass Sie eine Funktion haben, die aufgerufen wird, superAwesomeFunctionund diese Funktion kann einige super tolle Dinge tun. Sie können sie einer Variablen zuweisen, indem Sie sie nicht aufrufen, wie folgt:

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

Wenn Sie also SAF aufrufen, rufen Sie superAwesomeFunction oder Methode auf. Wenn Sie Ihren Lib-Ordner durchsuchen, werden Sie feststellen, dass die meisten Python- __builtin__Module so geschrieben sind. Dies geschieht, weil Sie manchmal einige Funktionen benötigen, die bestimmte Aufgaben ausführen, die für den Benutzer nicht ausreichen, aber für mehrere Funktionen erforderlich sind. Wenn Sie also die Wahl haben, dass Sie nicht zwei Funktionen mit dem Namen "superAwesomeFunction" haben können, können Sie "superAwesomeFunctionDoingBasicStuf" und "realSuperAwesomeFunction" haben und dann einfach die Variable "realSuperAwesomeFunction" in die Variable "superAwesomeFunction" einfügen und fertig.

Sie können den Speicherort der importierten Module finden, indem Sie in die Konsole eingeben importedModule.__file__(reales Beispiel import os;os.__file__). Folgen Sie diesem Verzeichnis einfach zu der Datei mit dem Namen importiertesModul.py, öffnen Sie sie im Editor und finden Sie heraus, wie Sie Ihr eigenes "Wissen" maximieren können.

Ich hoffe, das hilft Ihnen und vielleicht anderen Kollegen in Schwierigkeiten.

Danilo
quelle