Ist es möglich, einen Hinweis auf eine Lambda-Funktion einzugeben?

84

Derzeit können in Python die Parameter und Rückgabetypen einer Funktion wie folgt angegeben werden:

def func(var1: str, var2: str) -> int:
    return var1.index(var2)

Dies zeigt an, dass die Funktion zwei Zeichenfolgen akzeptiert und eine Ganzzahl zurückgibt.

Diese Syntax ist jedoch sehr verwirrend mit Lambdas, die wie folgt aussehen:

func = lambda var1, var2: var1.index(var2)

Ich habe versucht, Typhinweise sowohl für Parameter als auch für Rückgabetypen einzugeben, und ich kann keinen Weg finden, der keinen Syntaxfehler verursacht.

Ist es möglich, einen Hinweis auf eine Lambda-Funktion einzugeben? Wenn nicht, gibt es Pläne für Lambdas mit Typangaben oder einen Grund (neben dem offensichtlichen Syntaxkonflikt), warum nicht?

Nathan Merrill
quelle
Ich denke, dass der übliche Anwendungsfall für ein Lambda in einer anderen Funktion verschachtelt ist (im Gegensatz zu regulären Funktionen, die an einer Stelle definiert und an einem ganz anderen Ort aufgerufen werden). Wenn Sie die Typen in dieser Funktion bereits kennen, sollte es (im Prinzip) ziemlich einfach sein, herauszufinden, welche Typen Lambda erhalten wird. Wenn Sie außerdem die Syntax ändern, um Anmerkungen zu unterstützen, ist Ihr Lambda nur schwer lesbar.
mgilson
Warum willst du das tun? Die Lambda-Syntax ist für "Wegwerf" -Funktionen vorgesehen, die beispielsweise in sehr eingeschränkten Kontexten als keyArgument für die sortedintegrierte Funktion verwendet werden können . Ich sehe wirklich keinen Sinn darin, Typhinweise in solch begrenzten Kontexten hinzuzufügen. Wenn Sie außerdem variable PEP-526-Anmerkungen verwenden, um Typhinweise hinzuzufügen, lambdaum den Punkt IMHO vollständig zu verfehlen. Die lambdaSyntax soll anonyme Funktionen definieren . Was lambdabringt es, es zu verwenden und sofort an eine Variable zu binden? Einfach benutzen def!
Luciano Ramalho

Antworten:

91

Sie können in Python 3.6 und höher mithilfe von PEP 526-Variablenanmerkungen arbeiten . Sie können die Variable, der Sie das lambdaErgebnis zuweisen , mit dem typing.CallableGenerikum versehen :

from typing import Callable

func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)

Dadurch werden die Typhinweisinformationen nicht an das Funktionsobjekt selbst angehängt, sondern nur an den Namespace, in dem Sie das Objekt gespeichert haben. Dies ist jedoch normalerweise alles, was Sie für Typhinweiszwecke benötigen.

Sie können jedoch auch einfach eine Funktionsanweisung verwenden. Der einzige Vorteil eines lambdaAngebots besteht darin, dass Sie eine Funktionsdefinition für einen einfachen Ausdruck in einen größeren Ausdruck einfügen können. Das obige Lambda ist jedoch nicht Teil eines größeren Ausdrucks, sondern immer nur Teil einer Zuweisungsanweisung, die es an einen Namen bindet. Genau das würde eine def func(var1: str, var2: str): return var1.index(var2)Aussage bewirken.

Beachten Sie, dass Sie auch keine Anmerkungen *argsoder **kwargsArgumente separat erstellen können, wie in der Dokumentation für folgende CallableZustände angegeben:

Es gibt keine Syntax, um optionale oder Schlüsselwortargumente anzugeben. Solche Funktionstypen werden selten als Rückruftypen verwendet.

Diese Einschränkung gilt nicht für ein PEP 544- Protokoll mit einer __call__Methode . Verwenden Sie diese Option, wenn Sie eine aussagekräftige Definition benötigen, welche Argumente akzeptiert werden sollen. Sie benötigen Python 3.8 oder installieren das typing-extensionsProjekt für einen Backport:

from typing-extensions import Protocol

class SomeCallableConvention(Protocol):
    def __call__(var1: str, var2: str, spam: str = "ham") -> int:
        ...

func: SomeCallableConvention = lambda var1, var2, spam="ham": var1.index(var2) * spam

Für den lambdaAusdruck selbst können Sie keine Anmerkungen verwenden (die Syntax, auf der Pythons Typhinweise basieren). Die Syntax ist nur für defFunktionsanweisungen verfügbar .

Aus PEP 3107 - Funktionsanmerkungen :

Die Syntax von Lambda unterstützt keine Anmerkungen. Die Syntax von Lambda kann geändert werden, um Anmerkungen zu unterstützen, indem Klammern um die Parameterliste erforderlich sind. Es wurde jedoch beschlossen , diese Änderung nicht vorzunehmen, weil:

  • Es wäre eine inkompatible Änderung.
  • Lambdas sind sowieso kastriert.
  • Das Lambda kann immer in eine Funktion geändert werden.

Sie können die Anmerkungen weiterhin direkt an das Objekt anhängen. Das function.__annotations__Attribut ist ein beschreibbares Wörterbuch:

>>> def func(var1: str, var2: str) -> int:
...     return var1.index(var2)
...
>>> func.__annotations__
{'var1': <class 'str'>, 'return': <class 'int'>, 'var2': <class 'str'>}
>>> lfunc = lambda var1, var2: var1.index(var2)
>>> lfunc.__annotations__
{}
>>> lfunc.__annotations__['var1'] = str
>>> lfunc.__annotations__['var2'] = str
>>> lfunc.__annotations__['return'] = int
>>> lfunc.__annotations__
{'var1': <class 'str'>, 'return': <class 'int'>, 'var2': <class 'str'>}

Nicht, dass dynamische Annotationen wie diese Ihnen helfen würden, wenn Sie einen statischen Analysator über Ihre Typhinweise ausführen möchten.

Martijn Pieters
quelle
1
Wenn die Antwort ist, func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)warum ist dann keine bessere Antwort def func(var1: str, var2: str) -> int: return var1.index(var2)???
Guido van Rossum
@ GuidovanRossum: weil das die Antwort auf eine andere Frage ist. :-) Die Frage hier war, wie man Typhinweise auf ein Lambda anwendet, das das gleiche Ergebnis liefert wie das Definieren einer Funktion. Trotzdem werde ich einen Abschnitt hinzufügen, der beschreibt, warum Sie möglicherweise kein Lambda verwenden möchten.
Martijn Pieters
1
Nein, wörtlich genommen sollte die Antwort lauten: "Nein, es ist nicht möglich, es gibt keine Pläne, dies zu ändern, und die Gründe sind hauptsächlich syntaktisch - und es ist einfach genug, eine benannte Funktion zu definieren und diese zu kommentieren." Ich stelle auch fest, dass die vorgeschlagene Problemumgehung (Erstellen einer Variablen mit einem bestimmten aufrufbaren Typ) nicht sehr hilfreich ist, wenn das Lambda in einem großen Aufruf oder einer Datenstruktur vergraben ist, sodass es in keiner Weise "besser" ist als das Definieren einer Funktion. (Ich würde argumentieren, dass es schlimmer ist, weil die Callable-Notation nicht sehr gut lesbar ist.)
Guido van Rossum
Die Lösung lenkt auch ab, indem sie so aussieht, als würde sie eine Variable mit einem bestimmten Callable-Typ definieren, der während ihrer Lebensdauer verschiedene Lambdas zugewiesen werden könnten. Das ist etwas anderes als das Definieren einer Funktion (zumindest mypy balks, wenn Sie eine def neu definieren oder neu zuweisen, aber keine Variable vom Typ Callable) und weiter weg von der ursprünglichen Frage "Ich möchte nur ein Lambda mit Typanmerkungen." Ich behaupte weiterhin, dass es in diesem Fall keinen Vorteil hat, die Lambda-Form anstelle der Def-Form beizubehalten. (Und das würde ich sagen, auch wenn es keine Typanmerkungen gäbe.)
Guido van Rossum
1
@ GuidovanRossum: Kann ich Sie dann einladen, Ihre eigene Antwort zu schreiben? Mit Ihrem spezifischen Status als Vater der Sprache würde eine Antwort von Ihnen diese Antwort angesichts der Zeit leicht übertreffen, und Sie können genau das schreiben, was Sie für richtig halten.
Martijn Pieters
43

Seit Python 3.6 können Sie (siehe PEP 526 ):

from typing import Callable
is_even: Callable[[int], bool] = lambda x: (x % 2 == 0)
jan
quelle
Ich verstehe diese Antwort nicht: Wo stellen Sie den Typ ein x?
Stenci
3
@stenci Die is_evenFunktion ist eine Callable, die ein intArgument erwartet , also ist x eine int.
Januar
Ich verstehe jetzt. Auf den ersten Blick dachte ich, Sie kommentieren nur den vom Lambda zurückgegebenen Typ, nicht den Typ seiner Argumente.
Stenci
4
Dies ist keine Annotation des Lambda selbst: Sie können diese Annotationen nicht wie für eine annotierte Funktion aus dem Lambda-Objekt abrufen
cz
3
mypy 0.701 mit Python 3.7 prüft dies korrekt: is_even('x')Verursacht einen Tippfehler .
Konrad Rudolph
-1

Nein, es ist nicht möglich, es gibt keine Pläne, dies zu ändern, und die Gründe sind hauptsächlich syntaktisch.

Bart Louwers
quelle