메소드의 리턴 유형이 클래스 자체와 동일하도록 지정하려면 어떻게해야합니까?


410

파이썬 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)

그러나 내 편집자 (PyCharm)는 기준 위치를 ( __add__방법에서) 확인할 수 없다고 말합니다 . 반환 유형이 유형이되도록 기대하려면 어떻게해야 Position합니까?

편집 : 이것은 실제로 PyCharm 문제라고 생각합니다. 실제로 경고 및 코드 완성에 정보를 사용합니다.

그러나 내가 틀렸다면 정정하고 다른 구문을 사용해야합니다.

답변:


574

TL; DR : Python 4.0을 사용하는 경우 작동합니다. 3.7 이상에서 오늘 (2019)부터 미래 진술 ( from __future__ import annotations) 을 사용 하여이 기능을 켜야합니다 .Python 3.6 이하에서는 문자열을 사용하십시오.

이 예외가 있다고 생각합니다.

NameError: name 'Position' is not defined

Position파이썬 4를 사용하지 않는 한 주석에 사용하려면 먼저 정의해야 하기 때문 입니다.

파이썬 3.7 이상 : from __future__ import annotations

Python 3.7은 PEP 563 : 연기 된 주석 평가를 도입했습니다 . future 문을 사용하는 모듈은 from __future__ import annotations주석을 문자열로 자동 저장합니다.

from __future__ import annotations

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

이것은 Python 4.0에서 기본값이 될 예정입니다. 파이썬은 여전히 ​​동적으로 유형이 지정된 언어이므로 런타임에 유형 검사가 수행되지 않으므로 주석을 입력해도 성능에 영향을 미치지 않아야합니다. 잘못된! 파이썬 3.7로 사용되는 입력 모듈 전에 코어에서 가장 느린 파이썬 모듈 중 하나 그래서 당신이 만약 import typing당신이 볼 시간이 성능 증가 7까지 당신이 3.7로 업그레이드 할 때.

파이썬 <3.7 : 문자열 사용

PEP 484에 따르면 클래스 자체 대신 문자열을 사용해야합니다.

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

Django 프레임 워크를 사용하는 경우 Django 모델도 순방향 참조를 위해 문자열을 사용하므로 친숙 할 수 있습니다 (외래 모델이 self아직 선언되지 않았거나 아직 선언되지 않은 외국 키 정의 ). 이것은 Pycharm 및 기타 도구와 함께 작동해야합니다.

출처

여행을 절약하기 위해 PEP 484PEP 563 의 관련 부품 :

앞으로 참조

형식 힌트에 아직 정의되지 않은 이름이 포함 된 경우 해당 정의는 문자열 리터럴로 표현되어 나중에 확인할 수 있습니다.

이것이 일반적으로 발생하는 상황은 정의 된 클래스가 일부 메소드의 서명에서 발생하는 컨테이너 클래스의 정의입니다. 예를 들어 다음 코드 (간단한 이진 트리 구현 시작)는 작동하지 않습니다.

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

이를 해결하기 위해 다음과 같이 씁니다.

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

문자열 리터럴에는 유효한 Python 표현식 (예 : compile (lit, '', 'eval')은 유효한 코드 객체 여야 함)이 포함되어야하며 모듈이 완전히로드되면 오류없이 평가해야합니다. 평가되는 로컬 및 글로벌 네임 스페이스는 동일한 함수에 대한 기본 인수가 평가되는 동일한 네임 스페이스 여야합니다.

및 PEP 563 :

Python 4.0에서는 정의시 함수 및 변수 주석이 더 이상 평가되지 않습니다. 대신 문자열 형식이 각 __annotations__사전에 유지됩니다 . 정적 형식 검사기는 동작에 차이가없는 반면 런타임에 주석을 사용하는 도구는 연기 된 평가를 수행해야합니다.

...

위에서 설명한 기능은 다음 특수 가져 오기를 사용하여 Python 3.7부터 사용할 수 있습니다.

from __future__ import annotations

대신 유혹을받을 수있는 것들

A. 더미 정의 Position

클래스 정의 전에 더미 정의를 배치하십시오.

class Position(object):
    pass


class Position(object):
    ...

이것은 제거 NameError되고 OK처럼 보일 수도 있습니다.

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

하지만 그렇습니까?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

B. 주석을 추가하기위한 Monkey-patch :

주석을 추가하기 위해 파이썬 메타 프로그래밍 마술을 시도하고 클래스 정의를 원숭이 패치하기 위해 데코레이터를 작성할 수 있습니다.

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

데코레이터는 이에 상응하는 책임이 있습니다.

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

적어도 옳은 것 같습니다.

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

아마 너무 많은 문제입니다.

결론

3.6 이하를 사용하는 경우 클래스 이름을 포함하는 문자열 리터럴을 3.7에서 사용 from __future__ import annotations하면 작동합니다.


2
맞습니다. 이것은 PyCharm 문제가 아니며 Python 3.5 PEP 484 문제입니다. mypy 유형 도구를 통해 경고를 실행하면 동일한 경고가 표시됩니다.
Paul Everitt

23
> Python 4.0을 사용하는 경우에는 작동하지 않습니다. Sarah Connor를 보셨습니까? :)
scrutari

@JoelBerkeley 방금 테스트 한 결과 유형 매개 변수가 3.6에서 나를 위해 일했습니다 typing. 문자열을 평가할 때 사용하는 모든 유형이 범위에 있어야하므로 가져 오기를 잊지 마십시오 .
Paulo Scardine

아, 내 실수, 난 그냥 ''유형 매개 변수 가 아니라 클래스 를 돌고 있었다
joelb

5
사용하는 모든 사람에게 중요한 참고 사항 from __future__ import annotations-다른 모든 가져 오기 전에 가져와야합니다.
Artur

16

유형을 문자열로 지정하는 것은 좋지만 기본적으로 파서를 우회하고 있다는 사실에 항상 감사드립니다. 따라서 다음 리터럴 문자열 중 하나를 잘못 입력하지 않는 것이 좋습니다.

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

약간의 변형은 바인딩 된 typevar을 사용하는 것입니다. 적어도 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)

8
파이썬이 typing.Self이것을 명시 적으로 지정 하기 를 바랍니다 .
Alexander Huszagh 2016 년

2
나는 당신과 같은 것이 typing.Self존재 하는지 확인하기 위해 여기에 왔습니다 . 다형성을 활용할 때 하드 코딩 된 문자열을 반환하면 올바른 유형을 반환하지 못합니다. 필자의 경우 deserialize 클래스 메서드 를 구현하고 싶었습니다 . 나는 dict (kwargs)을 반환하고 전화하는 것에 정착했습니다 some_class(**some_class.deserialize(raw_data)).
Scott P.

여기에 사용 된 유형 주석은 서브 클래스를 사용하기 위해 올바르게 구현할 때 적합합니다. 그러나 구현은 Position클래스가 아닌을 반환 하므로 위의 예는 기술적으로 올바르지 않습니다. 구현은 Position(다음과 같이 대체되어야합니다 self.__class__(.
Sam Bull

또한 주석에 따르면 반환 유형은에 other따라 다르지만 실제로는에 따라 다릅니다 self. 에 그래서, 당신은 주석을 넣을 필요가 self올바른 동작을 설명하기 위해 (그리고 어쩌면 other단지해야 Position그것이 반환 형식에 묶여 아니에요 것을 보여주기 위해). 이 작업은 작업 만하는 경우에도 사용할 수 있습니다 self. 예 :def __aenter__(self: T) -> T:
Sam Bull

15

클래스 본문 자체를 구문 분석 할 때 'Position'이라는 이름을 사용할 수 없습니다. 타입 선언을 어떻게 사용하는지 모르겠지만 파이썬의 PEP 484-이 타이핑 힌트를 사용하면이 시점에서 단순히 이름을 문자열로 넣을 수 있다고 말하는 경우 가장 많이 사용해야하는 모드입니다.

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

https://www.python.org/dev/peps/pep-0484/#forward-references를 확인 하십시오 -준수하는 도구는 클래스 이름을 풀고 사용하는 것으로 알고 있습니다. 파이썬 언어 자체는 이러한 주석을 수행하지 않습니다. 일반적으로 정적 코드 분석을위한 것이거나 런타임에 유형 검사를위한 라이브러리 / 프레임 워크를 가질 수 있지만 명시 적으로 설정해야합니다.

업데이트 파이썬 3.8로, 체크, 또한 격려-563은 파이썬 3.8로 쓸 수 있습니다 - from __future__ import annotations주석의 평가를 연기 - 앞으로 참조하는 클래스를 간단하게 작동합니다.


9

문자열 기반 유형 힌트가 허용되는 경우 __qualname__항목을 사용할 수도 있습니다. 클래스 이름을 보유하며 클래스 정의 본문에서 사용할 수 있습니다.

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

이렇게하면 클래스 이름을 바꾸더라도 유형 힌트가 수정되지 않습니다. 그러나 나는 개인적으로 스마트 코드 편집기 가이 양식을 잘 처리하지 않을 것이라고 생각합니다.


1
클래스 이름을 하드 코딩하지 않으므로 하위 클래스에서 계속 작동하기 때문에 특히 유용합니다.
Florian Brucker

연기 된 주석 평가 (PEP 563)와 함께 작동하는지 확실하지 않으므로 이에 대한 질문을했습니다 .
Florian Brucker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.