programing

둘러싸는 클래스의 타입을 가진 메서드를 입력하려면 어떻게 해야 합니까?

goodsources 2022. 10. 11. 22:52
반응형

둘러싸는 클래스의 타입을 가진 메서드를 입력하려면 어떻게 해야 합니까?

Python 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'이라고 합니다.Position할 수 없다__add__반환 타입?Position

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

하지만 제가 틀렸거나 다른 구문을 사용해야 한다면 정정해 주세요.

TL;DR: 오늘(2019년) 현재 Python 3.7+에서는 이 기능을 "future" 스테이트먼트를 사용할 수 있습니다.from __future__ import annotations.

에 의해 하게 된 .from __future__ import annotations 향후 Python 버전에서는 기본값이 될 수 있으며 Python 3.10에서는 기본값이 될 예정입니다.다만, 3.10 의 변경은 마지막 순간에 되돌려져, 현재는 전혀 행해지지 않는 경우가 있습니다).

Python 3.6 이하에서는 문자열을 사용해야 합니다.


다음과 같은 예외가 있는 것 같습니다.

NameError: name 'Position' is not defined

그 이유는PositionPEP 563 변경을 활성화한 상태에서 Python을 사용하지 않는 한 주석에서 사용하려면 먼저 정의해야 합니다.

Python 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 3.10에서 기본값이 될 예정이었지만, 현재 이 변경은 연기되었습니다.Python은 여전히 동적으로 입력되는 언어이기 때문에 실행 시 타입 체크를 하지 않기 때문에 주석 입력은 성능에 영향을 주지 않습니다.틀렸습니다! Python 3.7 이전에는 입력 모듈이 코어에서 가장 느린 파이썬 모듈하나였기 때문에 모듈을 Import하는 코드의 경우 3.7로 업그레이드하면 성능최대 7배 향상됩니다.

Python <3.7: 문자열 사용

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

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

Framework를 수 이 Django Framework인 경우 키 ).self을 사용법)이것은 Pycharm과 다른 도구와 함께 작동해야 합니다.

원천

PEP 484 및 PEP 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 3.10에서는 정의 시 함수 및 변수 주석이 더 이상 평가되지 않습니다.문자열 은 각각의 형식으로 됩니다.__annotations__ 시 주석을 하는 툴은 평가를.정적 유형 체커는 동작에 차이가 없는 반면 런타임에 주석을 사용하는 도구는 연기된 평가를 수행해야 합니다.

...

Python 3.7에서 향후 동작 활성화

위에서 설명한 기능은 다음 특수 Import를 사용하여 Python 3.7부터 활성화할 수 있습니다.

from __future__ import annotations

대신 하고 싶은 일

더미 A를 합니다. 더미의 정의Position

클래스 정의 전에 더미 정의를 배치합니다.

class Position(object):
    pass


class Position(object):
    ...

렇게면,가 없어집니다.NameError정상적으로 보일 수도 있습니다.

>>> 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:

Python 메타프로그래밍 마법을 사용해 보고 주석을 추가하기 위해 클래스 정의를 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

아마 문제가 너무 많을 거야.

Python 3.11(2022년 말 출시 예정)부터 반품 타입으로 사용할 수 있게 되었습니다.

from typing import Self


class Position:

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

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

Self, 이 내용에도 되어 있습니다.typing-extensions에서 사용 는라이브러리의 PyPi(PyPi입니다.typing모듈.https://pypi.org/project/typing-extensions/, 에서

typing_extensions 모듈은 다음 두 가지 관련 목적을 수행합니다.

  • 이전 Python 버전에서 새로운 유형의 시스템 기능을 사용할 수 있습니다.예를 들어, 타이핑.TypeGuard는 Python 3.10에서 처음이지만 type_extensions를 사용하면 Python 3.6에서 3.9까지 사용할 수 있습니다.
  • 새로운 유형의 시스템 PEP가 받아들여지고 입력 모듈에 추가되기 전에 해당 PEP를 사용한 실험을 활성화합니다.

현재의,typing-extensions는 공식적으로 Python 3.7 이후를 지원합니다.

유형을 문자열로 지정하는 것은 괜찮지만 기본적으로 파서를 우회하고 있다는 점이 항상 짜증납니다.따라서 다음 문자열 중 하나의 철자를 틀리지 않도록 하십시오.

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

약간 변형된 것은 바인딩된 typevar를 사용하는 것입니다.이 경우 typevar를 선언할 때 스트링을 적어도1회만 쓸 필요가 있습니다.

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)

클래스 본문 자체를 구문 분석할 때는 '위치'라는 이름을 사용할 수 없습니다.타입 선언을 어떻게 사용하고 있는지는 모르겠지만, Python의 PEP 484는 대부분의 모드에서 사용하는 것입니다.이러한 타이핑 힌트를 사용하면, 이 시점에서 그 이름을 문자열로서 간단하게 입력할 수 있습니다.

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

PEP 484 섹션의 forward references를 체크해 주세요.-에 준거한 툴은 클래스 이름을 거기에서 언랩하여 사용하는 방법을 알고 있습니다.(Python 언어 자체는 이러한 주석을 사용하지 않는 것을 항상 염두에 두는 것이 중요합니다.보통 정적 코드 분석을 위한 것이거나 런타임에 유형 검사를 위한 라이브러리/프레임워크를 가질 수 있지만, 명시적으로 설정해야 합니다.)

업데이트: 또한 Python 3.7부터는 PEP 563을 확인하십시오.Python 3.8 에서는 기입이 가능합니다.from __future__ import annotations주석 평가를 연기합니다.정방향 참조 클래스는, 간단하게 동작할 필요가 있습니다.

업데이트 2: Python 3.10 현재 PEP 563이 재시도되고 있으며, PEP 649가 대신 사용될 수 있습니다.따옴표 없이 클래스 이름을 사용할 수 있도록 하는 것이 권장 사항입니다.

업데이트 3: Python 3.11(2022년 말 출시 예정)부터 이용 가능typing.Self이 목적을 위해 설계되었습니다.PEP 673을 확인합니다!위에서 설명한 전송 참조를 해결하기 위한 PEP 563 및 649는 여전히 경합하고 있으며 현재와 같이 진행되지 않을 가능성이 높습니다.

것만 NameError: name 'Position' is not defined클래스 이름을 문자열로 지정할 수 있습니다.

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

또는 Python 3.7 이상을 사용하는 경우 코드 상단에 다음 행을 추가합니다(다른 항목이 가져오기 직전).

from __future__ import annotations

단, 서브클래스에 대해서도 이 기능을 하고 특정 서브클래스를 반환하는 경우 메서드에 일반적인 메서드로 주석을 붙일 필요가 있습니다.TypeVar.

은 '우리'가TypeVar self이 이 반환되는 것을 __add__() ★★★★★★★★★★★★★★★★★」copy()입니다.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)

힌트가 경우, 「 」는 「 」를 참조해 주세요.__qualname__아이템도 사용할 수 있습니다.클래스 이름이 유지되며 클래스 정의 본문에서 사용할 수 있습니다.

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

이렇게 함으로써 클래스 이름을 변경하더라도 유형 힌트를 수정하는 것은 아닙니다.하지만 저는 개인적으로 스마트 코드 에디터가 이 양식을 잘 다루리라곤 기대하지 않습니다.

edit: @carampa.carambillaga가 더 나은 방법을 알려주었습니다.https://stackoverflow.com/a/63237226를 참조하십시오.

아래의 답변 대신 위의 답변을 하는 것이 좋습니다.

[후세를 위해 보관 중인 아래 오래된 답변]

I ❤️ 파울로의 답변

그러나 자신에 대한 유형 힌트 상속에는 주의해야 할 점이 있습니다. 즉, 클래스 이름의 리터럴 복사 붙여넣기를 문자열로 사용하여 힌트를 입력하면 유형 힌트가 올바르거나 일관된 방식으로 상속되지 않습니다.

이에 대한 해결책은 함수 자체의 반환에 유형 힌트를 넣어 반환 유형 힌트를 제공하는 것입니다.

✅ 예를 들어, 다음을 수행합니다.

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

이렇게 하는 대신:

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

아래는 위와 같이 회전식 ✅ 방식으로 유형 힌트를 하려는 이유입니다.

class StaticChild(StaticParent):
  pass

class DynamicChild(DynamicParent):
  pass

static_child = StaticChild()
dynamic_child = DynamicChild()

dynamic_child스크린샷은 자신을 참조할 때 유형 힌트가 올바르게 기능함을 보여줍니다.

여기에 이미지 설명 입력

static_child가 부모 있는 타입 과 함께 즉, 타입 힌트는 상속과 함께 올바르게 변경되지 않습니다.이것은, 「유형 힌트」가 됩니다.static 할 것이기 입니다.

여기에 이미지 설명 입력

언급URL : https://stackoverflow.com/questions/33533148/how-do-i-type-hint-a-method-with-the-type-of-the-enclosing-class

반응형