TL; DR : jeśli używasz Python 4.0, to po prostu działa. Na dzień dzisiejszy (2019) w wersji 3.7+ musisz włączyć tę funkcję, używając przyszłej instrukcji ( from __future__ import annotations
) - w Pythonie 3.6 lub niższym użyj ciągu znaków.
Myślę, że masz ten wyjątek:
NameError: name 'Position' is not defined
Wynika to z faktu, że Position
należy go zdefiniować, zanim będzie można go użyć w adnotacji, chyba że używa się języka Python 4.
Python 3.7+: from __future__ import annotations
Python 3.7 wprowadza PEP 563: odroczona ocena adnotacji . Moduł korzystający z przyszłej instrukcji from __future__ import annotations
automatycznie zapisuje adnotacje jako ciągi znaków:
from __future__ import annotations
class Position:
def __add__(self, other: Position) -> Position:
...
Jest to zaplanowane jako domyślne w Pythonie 4.0. Ponieważ Python nadal jest językiem o dynamicznym pisaniu, więc sprawdzanie typów nie jest wykonywane w czasie wykonywania, adnotacje podczas pisania nie powinny mieć wpływu na wydajność, prawda? Źle! Przed Pythonie 3.7 modułu wpisując kiedyś jeden z najwolniejszych modułów Python w rdzeniu więc jeśli was import typing
widać aż do 7-krotny wzrost wydajności podczas aktualizacji do 3.7.
Python <3.7: użyj ciągu
Według PEP 484 powinieneś użyć ciągu zamiast samej klasy:
class Position:
...
def __add__(self, other: 'Position') -> 'Position':
...
Jeśli używasz frameworka Django, może to być znane, ponieważ modele Django używają również ciągów dla referencji do przodu (definicje kluczy obcych, w których model obcy jest self
lub nie został jeszcze zadeklarowany). Powinno to działać z Pycharmem i innymi narzędziami.
Źródła
Odpowiednie części PEP 484 i PEP 563 , aby oszczędzić ci podróży:
Prześlij referencje
Gdy podpowiedź typu zawiera nazwy, które nie zostały jeszcze zdefiniowane, definicję tę można wyrazić dosłownie jako ciąg znaków, co zostanie rozwiązane później.
Sytuacja, w której zdarza się to często, to definicja klasy kontenera, w której definiowana klasa występuje w sygnaturze niektórych metod. Na przykład następujący kod (początek prostej implementacji drzewa binarnego) nie działa:
class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right
Aby rozwiązać ten problem, piszemy:
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
Literał łańcuchowy powinien zawierać prawidłowe wyrażenie w języku Python (tj. Kompilacja (lit, '', 'eval') powinna być prawidłowym obiektem kodu) i powinna być oceniana bez błędów po pełnym załadowaniu modułu. Lokalna i globalna przestrzeń nazw, w której jest obliczana, powinny być tymi samymi przestrzeniami nazw, w których oceniane byłyby domyślne argumenty tej samej funkcji.
i PEP 563:
W Pythonie 4.0 adnotacje funkcji i zmiennych nie będą już oceniane w czasie definiowania. Zamiast tego w odpowiednim __annotations__
słowniku zachowana zostanie forma łańcucha . Mechanizmy sprawdzania typu statycznego nie zauważą żadnej różnicy w zachowaniu, podczas gdy narzędzia używające adnotacji w czasie wykonywania będą musiały wykonać odroczoną ocenę.
...
Funkcję opisaną powyżej można włączyć począwszy od Pythona 3.7, korzystając z następującego specjalnego importu:
from __future__ import annotations
Rzeczy, które możesz pokusić się zamiast tego
A. Zdefiniuj manekina Position
Przed definicją klasy umieść fikcyjną definicję:
class Position(object):
pass
class Position(object):
...
Spowoduje to usunięcie NameError
i może nawet wyglądać OK:
>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}
Ale czy to jest?
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: False
other is Position: False
B. Łatka-małpa w celu dodania adnotacji:
Możesz wypróbować magię programowania w języku Python i napisać dekorator, aby załatać definicję klasy w celu dodania adnotacji:
class Position:
...
def __add__(self, other):
return self.__class__(self.x + other.x, self.y + other.y)
Dekorator powinien być odpowiedzialny za równowartość tego:
Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position
Przynajmniej wydaje się słuszne:
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: True
other is Position: True
Prawdopodobnie za dużo kłopotów.
Wniosek
Jeśli używasz wersji 3.6 lub niższej, użyj literału łańcuchowego zawierającego nazwę klasy, w wersji 3.7 użyj from __future__ import annotations
i to po prostu zadziała.
typing
ponieważ każdy typ, którego używasz, musi być objęty zakresem podczas oceny łańcucha.''
okrągły klasę, a nie parametry typufrom __future__ import annotations
- należy ją zaimportować przed wszystkimi innymi importami.Określenie typu jako łańcucha jest w porządku, ale zawsze docenia mnie to, że w zasadzie omijamy parser. Więc lepiej nie pisuj źle żadnego z tych literalnych ciągów:
Niewielką odmianą jest użycie związanego typu maszynowego, przynajmniej wtedy, gdy deklarujesz typograficzny, musisz napisać ciąg tylko raz:
źródło
typing.Self
to wyraźnie określić.typing.Self
istnieje coś takiego jak twój . Zwrócenie łańcucha zakodowanego na stałe nie zwraca poprawnego typu przy wykorzystaniu polimorfizmu. W moim przypadku chciałem wdrożyć metodę klasy deserializacji . Postanowiłem zwrócić dyktando (kwargs) i zadzwonićsome_class(**some_class.deserialize(raw_data))
.Position
, a nie klasę, więc powyższy przykład jest technicznie niepoprawny. Implementacja powinna zostać zastąpionaPosition(
czymś takimself.__class__(
.other
, ale najprawdopodobniej zależy od tegoself
. Trzeba więc umieścić adnotację,self
aby opisać prawidłowe zachowanie (a możeother
po prostuPosition
pokazać, że nie jest związana z typem zwrotu). Można tego również użyć w przypadkach, gdy pracujesz tylkoself
. np.def __aenter__(self: T) -> T:
Nazwa „Pozycja” nie jest dostępna w czasie analizy samego ciała klasy. Nie wiem, w jaki sposób używasz deklaracji typu, ale PEP 484 Pythona - tego powinien używać większość trybów, jeśli korzystając z tych wskazówek dotyczących pisania, możesz po prostu umieścić nazwę jako ciąg znaków w tym miejscu:
Sprawdź https://www.python.org/dev/peps/pep-0484/#forward-references - narzędzia zgodne z tym będą wiedziały, jak rozpakować stamtąd nazwę klasy i z niej skorzystać (zawsze ważne jest, aby mieć pamiętając, że sam język Python nie robi nic z tych adnotacji - są one zwykle przeznaczone do analizy kodu statycznego lub można mieć bibliotekę / strukturę do sprawdzania typu w czasie wykonywania - ale trzeba to wyraźnie ustawić).
aktualizacja Ponadto, począwszy od Python 3.8, sprawdź pep-563 - od Python 3.8 można pisać,
from __future__ import annotations
aby odroczyć ocenę adnotacji - klasy referencyjne powinny działać od razu.źródło
Gdy podpowiedź typu tekstowego jest akceptowalna,
__qualname__
można również użyć elementu. Zawiera nazwę klasy i jest dostępna w treści definicji klasy.W ten sposób zmiana nazwy klasy nie oznacza modyfikacji podpowiedzi typu. Ale osobiście nie spodziewałbym się, że inteligentne edytory kodu dobrze poradzą sobie z tym formularzem.
źródło