TL; DR : Wenn Sie Python 4.0 verwenden, funktioniert es einfach. Ab heute (2019) in Version 3.7+ müssen Sie diese Funktion mithilfe einer zukünftigen Anweisung ( from __future__ import annotations
) aktivieren. Verwenden Sie für Python 3.6 oder niedriger eine Zeichenfolge.
Ich denke du hast diese Ausnahme:
NameError: name 'Position' is not defined
Dies liegt daran, Position
dass definiert werden muss, bevor Sie es in einer Anmerkung verwenden können, es sei denn, Sie verwenden Python 4.
Python 3.7+: from __future__ import annotations
Python 3.7 wird eingeführt PEP 563 ein: Verschiebung der Auswertung von Anmerkungen . Ein Modul, das die zukünftige Anweisung verwendet from __future__ import annotations
, speichert Anmerkungen automatisch als Zeichenfolgen:
from __future__ import annotations
class Position:
def __add__(self, other: Position) -> Position:
...
Dies soll in Python 4.0 zum Standard werden. Da Python immer noch eine dynamisch typisierte Sprache ist und zur Laufzeit keine Typprüfung durchgeführt wird, sollten Tippanmerkungen keine Auswirkungen auf die Leistung haben, oder? Falsch! Vor Python 3.7 war das Typisierungsmodul eines der langsamsten Python-Module im Kern so , wenn Sie import typing
Sie werden sehen , bis zu 7 - mal in der Leistung zu erhöhen , wenn Sie auf 3,7 aktualisieren.
Python <3.7: Verwenden Sie eine Zeichenfolge
Nach PEP 484 sollten Sie anstelle der Klasse selbst eine Zeichenfolge verwenden:
class Position:
...
def __add__(self, other: 'Position') -> 'Position':
...
Wenn Sie das Django-Framework verwenden, ist dies möglicherweise bekannt, da Django-Modelle auch Zeichenfolgen für Vorwärtsreferenzen verwenden (Fremdschlüsseldefinitionen, bei denen das Fremdmodell self
noch deklariert ist oder nicht). Dies sollte mit Pycharm und anderen Tools funktionieren.
Quellen
Die relevanten Teile von PEP 484 und PEP 563 , um Ihnen die Reise zu ersparen:
Referenzen weiterleiten
Wenn ein Typhinweis Namen enthält, die noch nicht definiert wurden, kann diese Definition als Zeichenfolgenliteral ausgedrückt werden, um später aufgelöst zu werden.
Eine Situation, in der dies häufig vorkommt, ist die Definition einer Containerklasse, in der die zu definierende Klasse in der Signatur einiger Methoden vorkommt. Der folgende Code (der Beginn einer einfachen Implementierung eines Binärbaums) funktioniert beispielsweise nicht:
class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right
Um dies zu beheben, schreiben wir:
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
Das String-Literal sollte einen gültigen Python-Ausdruck enthalten (dh compile (lit, '', 'eval') sollte ein gültiges Codeobjekt sein) und nach dem vollständigen Laden des Moduls fehlerfrei ausgewertet werden. Der lokale und globale Namespace, in dem er ausgewertet wird, sollten dieselben Namespaces sein, in denen Standardargumente für dieselbe Funktion ausgewertet werden.
und PEP 563:
In Python 4.0 werden Funktions- und Variablenanmerkungen zur Definitionszeit nicht mehr ausgewertet. Stattdessen wird eine Zeichenfolgenform im jeweiligen __annotations__
Wörterbuch beibehalten . Statische Typprüfungen sehen keinen Unterschied im Verhalten, während Tools, die zur Laufzeit Anmerkungen verwenden, eine verschobene Auswertung durchführen müssen.
...
Die oben beschriebene Funktionalität kann ab Python 3.7 mit dem folgenden speziellen Import aktiviert werden:
from __future__ import annotations
Dinge, zu denen Sie möglicherweise versucht sind
A. Definieren Sie einen Dummy Position
Platzieren Sie vor der Klassendefinition eine Dummy-Definition:
class Position(object):
pass
class Position(object):
...
Dies wird das loswerden NameError
und kann sogar OK aussehen:
>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}
Aber ist es?
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: False
other is Position: False
B. Affen-Patch, um die Anmerkungen hinzuzufügen:
Vielleicht möchten Sie etwas Python-Meta-Programmiermagie ausprobieren und einen Dekorator schreiben, um die Klassendefinition mit Affen zu patchen, um Anmerkungen hinzuzufügen:
class Position:
...
def __add__(self, other):
return self.__class__(self.x + other.x, self.y + other.y)
Der Dekorateur sollte für das Äquivalent verantwortlich sein:
Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position
Zumindest scheint es richtig:
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: True
other is Position: True
Wahrscheinlich zu viel Ärger.
Fazit
Wenn Sie 3.6 oder niedriger verwenden, verwenden Sie ein Zeichenfolgenliteral, das den Klassennamen enthält. Verwenden Sie in 3.7 from __future__ import annotations
und es funktioniert einfach.
typing
da jeder Typ, den Sie verwenden, im Gültigkeitsbereich sein muss, wenn die Zeichenfolge ausgewertet wird.''
die Klasse umrundet, nicht die Typparameterfrom __future__ import annotations
- dieser muss vor allen anderen Importen importiert werden.Das Angeben des Typs als Zeichenfolge ist in Ordnung, aber es regt mich immer ein bisschen auf, dass wir den Parser im Grunde umgehen. Sie sollten also keine dieser wörtlichen Zeichenfolgen falsch schreiben:
Eine geringfügige Abweichung besteht darin, einen gebundenen Typevar zu verwenden. Zumindest müssen Sie den String dann nur einmal schreiben, wenn Sie den Typevar deklarieren:
quelle
typing.Self
dies explizit spezifizieren müssen.typing.Self
existiert. Wenn Sie eine fest codierte Zeichenfolge zurückgeben, wird bei Verwendung des Polymorphismus nicht der richtige Typ zurückgegeben. In meinem Fall wollte ich eine Deserialisierungsklassenmethode implementieren. Ich entschied mich dafür, ein Diktat (kwargs) zurückzugeben und anzurufensome_class(**some_class.deserialize(raw_data))
.Position
wird jedoch zurückgegeben und nicht die Klasse. Daher ist das obige Beispiel technisch nicht korrekt. Die Implementierung sollte durch soPosition(
etwas ersetzt werdenself.__class__(
.other
, aber höchstwahrscheinlich tatsächlich vonself
. Sie müssten also die Annotation aktivierenself
, um das korrekte Verhalten zu beschreiben (undother
sollten möglicherweise nurPosition
zeigen, dass es nicht an den Rückgabetyp gebunden ist). Dies kann auch für Fälle verwendet werden, in denen Sie nur mit arbeitenself
. zBdef __aenter__(self: T) -> T:
Der Name 'Position' ist zum Zeitpunkt der Analyse des Klassenkörpers selbst nicht verfügbar. Ich weiß nicht, wie Sie die Typdeklarationen verwenden, aber Pythons PEP 484 - was die meisten Modi verwenden sollten, wenn Sie diese Tipphinweise verwenden, besagt, dass Sie den Namen an dieser Stelle einfach als Zeichenfolge einfügen können:
Überprüfen Sie https://www.python.org/dev/peps/pep-0484/#forward-references - Tools, die dem entsprechen, können den Klassennamen von dort auspacken und verwenden. (Dies ist immer wichtig Denken Sie daran, dass die Python-Sprache selbst nichts von diesen Anmerkungen macht - sie sind normalerweise für die Analyse von statischem Code gedacht, oder man könnte eine Bibliothek / ein Framework für die Typprüfung zur Laufzeit haben - aber das müssen Sie explizit festlegen).
Update Überprüfen Sie ab Python 3.8 auch pep-563 - ab Python 3.8 ist es möglich zu schreiben
from __future__ import annotations
, um die Auswertung von Anmerkungen zu verschieben - Vorwärtsreferenzierungsklassen sollten problemlos funktionieren.quelle
Wenn ein auf Zeichenfolgen basierender Typhinweis akzeptabel ist, kann das
__qualname__
Element auch verwendet werden. Es enthält den Namen der Klasse und ist im Hauptteil der Klassendefinition verfügbar.Auf diese Weise bedeutet das Umbenennen der Klasse nicht, dass die Typhinweise geändert werden. Ich persönlich würde jedoch nicht erwarten, dass Smart-Code-Editoren mit diesem Formular gut umgehen.
quelle