Jak wpisać metodę z typem klasy?

Mam następujący kod w Pythonie 3:

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

Ale mój edytor (PyCharm) mówi, że pozycja odniesienia nie może być rozwiązana(w metodzie __add__). Jak należy określić, że oczekuję, że typ zwracany będzie typu Position?

Edit: myślę, że to jest rzeczywiście problem PyCharm. W rzeczywistości używa informacji w swoich ostrzeżeniach i uzupełnianiu kodu

Ale popraw mnie, jeśli się mylę i muszę użyć innej składni.

Author: Carcigenicate, 2015-11-04

6 answers

TL; DR : Jeśli używasz Pythona 3.10 lub nowszego, to po prostu działa. Od dzisiaj (2019) w wersji 3.7+ musisz włączyć tę funkcję używając wyrażenia future (from __future__ import annotations) - dla Pythona 3.6 lub poniżej użyj ciągu znaków.

Chyba masz ten wyjątek:

NameError: name 'Position' is not defined

To dlatego, że Position musi być zdefiniowany, zanim będzie można go użyć w adnotacji, chyba że używasz Pythona 3.10 lub nowszego.

Python 3.7+: from __future__ import annotations

Python 3.7 wprowadza PEP 563: przełożona ocena adnotacje . Moduł, który używa wyrażenia future from __future__ import annotations będzie automatycznie zapisywał adnotacje jako ciągi znaków:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

To ma być domyślne w Pythonie 3.10. Ponieważ Python nadal jest językiem dynamicznie wpisywanym, więc nie sprawdzanie typu jest wykonywane w czasie wykonywania, wpisywanie adnotacji nie powinno mieć wpływu na wydajność, prawda? Źle! Przed Pythonem 3.7 moduł typowania był jednym z najwolniejszych modułów Pythona w core więc jeśli {[16] } zobaczysz w górę do 7-krotnego wzrostu wydajności Po uaktualnieniu do wersji 3.7.

Python

Zgodnie z PEP 484, powinieneś użyć ciągu znaków 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 również używają łańcuchów do przekazywania referencji (definicje klucza obcego, gdzie model obcy jest self lub nie jest jeszcze zadeklarowany). Powinno to działać z Pycharm i innymi narzędzia.

Źródła

Odpowiednie części PEP 484 i PEP 563, aby oszczędzić Ci podróży:

Forward references

Gdy podpowiedź typu zawiera nazwy, które nie zostały jeszcze zdefiniowane, definicja ta może być wyrażona jako literał Łańcuchowy, który zostanie rozwiązany później.

Sytuacja, w której występuje to powszechnie, to definicja klasy kontenera, w której zdefiniowana Klasa występuje w podpisie niektórych z metody. 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 się tym zająć, piszemy:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

Łańcuch znaków powinien zawierać poprawne wyrażenie Pythona (tzn. compile(lit, ", 'eval') powinien być prawidłowym obiektem kodu) i powinien być oceniany bez błędów po pełnym załadowaniu modułu. Lokalna i globalna przestrzeń nazw, w której jest oceniana, powinny być tymi samymi przestrzeniami nazw, w których domyślne argumenty tej samej funkcji będą oceniane.

I PEP 563:

W Pythonie 3.10 adnotacje funkcji i zmiennych nie będą już oceniane w czasie definiowania. Zamiast tego, w odpowiednim słowniku __annotations__ zachowana zostanie forma łańcuchowa. Statyczne Kontrolery typów nie będą widzieć różnicy w zachowaniu, podczas gdy narzędzia wykorzystujące adnotacje w czasie wykonywania będą musiały wykonać przełożoną ocenę.

...

The funkcjonalność opisana powyżej może być włączona począwszy od Pythona 3.7 przy użyciu następującego specjalnego importu:

from __future__ import annotations

Rzeczy, które możesz pokusić się o zamiast tego

A. Zdefiniuj manekina Position

Przed definicją klasy umieść atrapę definicji:

class Position(object):
    pass


class Position(object):
    ...

To pozbędzie się NameError i może nawet wyglądać OK:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}
Ale czy tak 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. małpa-łatka w celu dodania adnotacji:

Możesz spróbować Meta Pythona programowanie magii i pisanie dekoratora aby dodać adnotacje do definicji klasy:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

Dekorator powinien być odpowiedzialny za odpowiednik 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.

Podsumowanie

Jeśli używasz 3.6 lub poniżej, użyj literału zawierającego nazwę klasy, w 3.7 użyj {[12] } i to po prostu zadziała.

 835
Author: Paulo Scardine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-27 08:47:22

Podanie typu jako string jest w porządku, ale zawsze trochę mnie denerwuje, że w zasadzie omijamy parser. Więc lepiej nie literuj źle żadnego z tych literalnych ciągów:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Lekką odmianą jest użycie bound typevar, przynajmniej wtedy musisz napisać łańcuch tylko raz, deklarując typevar:

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: T) -> T:
        return Position(self.x + other.x, self.y + other.y)
 20
Author: vbraun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-11 19:45:08

Nazwa 'Position' nie jest dostępna w momencie parsowania samego ciała klasowego. Nie wiem, jak używasz deklaracji typu, ale Pythona PEP 484-który jest tym, co większość trybów powinna używać, Jeśli za pomocą tych wskazówek typowania mówią, że można po prostu umieścić nazwę jako ciąg znaków w tym momencie:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Sprawdź https://www.python.org/dev/peps/pep-0484/#forward-references - narzędzia odpowiadające temu będą wiedzieć, aby rozpakować stamtąd nazwę klasy i z niej skorzystać.(Jest zawsze ważne jest, aby pamiętać, że sam język Python nie robi nic z tych adnotacji - są one zwykle przeznaczone do analizy kodu statycznego, lub można mieć bibliotekę / framework do sprawdzania typu w czasie wykonywania - ale trzeba to wyraźnie ustawić).

Update również, począwszy od Pythona 3.7, sprawdź pep-563 - począwszy od Pythona 3.8 możliwe jest pisanie from __future__ import annotations w celu odroczenia oceny adnotacji - klasy odwołujące do przodu powinny działać bezproblemowo.

 16
Author: jsbueno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-28 12:35:26

Gdy dopuszczalna jest podpowiedź typu string, można również użyć elementu __qualname__. Posiada nazwę klasy i jest dostępna w treści definicji klasy.

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()

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 tą formą.

 12
Author: Yvon DUTAPIS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-23 20:00:25

Jeśli zależy Ci tylko na poprawieniu NameError: name 'Position' is not defined, możesz podać nazwę klasy jako ciąg znaków:

def __add__(self, other: 'Position') -> 'Position':

Lub jeśli używasz Pythona 3.7 lub nowszego, Dodaj następujący wiersz na górze kodu (tuż przed innymi importami)

from __future__ import annotations

Jeśli jednak chcesz, aby to działało również dla podklas i zwracało określoną podklasę, musisz użyć klasy Generic, definiując TypeVar.

To, co jest nieco rzadkie, to to, że TypeVar jest związane z typem self. Zasadniczo to typowanie podpowiedzi mówi sprawdzającemu Typ, że typ zwracany __add__() i copy() jest tym samym typem co self.

from __future__ import annotations

from typing import TypeVar

T = TypeVar('T', bound=Position)

class Position:
    
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y
    
    def __add__(self: T, other: Position) -> T:
        return type(self)(self.x + other.x, self.y + other.y)
    
    def copy(self: T) -> T:
        return type(self)(self.x, self.y)
 7
Author: MacFreek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-26 18:38:58

I ❤ ️ Paulo ' s answer

Należy jednak zwrócić uwagę na dziedziczenie podpowiedzi typu w odniesieniu do jaźni, czyli jeśli wpiszesz podpowiedź używając dosłownej kopiuj wklej nazwy klasy jako ciąg znaków, wtedy podpowiedź typu nie będzie dziedziczona w prawidłowy lub spójny sposób.

Rozwiązaniem tego problemu jest dostarczenie podpowiedzi typu return poprzez umieszczenie podpowiedzi typu na return w samej funkcji.

Na przykład zrób to:

class DynamicParent:
  def func(self):
    # roundabout way of returning self in order to have inherited type hints of the return
    # https://stackoverflow.com/a/64938978
    _self:self.__class__ = self
    return _self

zamiast robić to:

class StaticParent:
  def func(self) -> 'StaticParent':
    return self

Poniżej znajduje się powód, dla którego chcesz wykonać podpowiedź typu za pomocą Ronda pokazanego powyżej

class StaticChild(StaticParent):
  pass

class DynamicChild(DynamicParent):
  pass

static_child = StaticChild()
dynamic_child = DynamicChild()

dynamic_child zrzut ekranu pokazuje, że podpowiedzi typu działają poprawnie podczas odwoływania się do jaźni:

Tutaj wpisz opis obrazka

static_child zrzut ekranu pokazuje, że podpowiedź typu jest błędnie wskazywana na klasę nadrzędną, tzn. podpowiedź typu nie zmienia się poprawnie wraz z dziedziczeniem; jest to static, ponieważ zawsze będzie wskazywać na rodzica, nawet gdy powinien wskazywać na klasę nadrzędną dziecko

Tutaj wpisz opis obrazka

 2
Author: user2426679,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-26 18:29:17