我在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)说引用位置不能解析(在__add__方法中)。我应该如何指定我期望返回类型为Position类型?

编辑:我认为这实际上是一个PyCharm问题。它实际上在警告和代码补全中使用了这些信息。

但如果我错了,请纠正我,并需要使用其他语法。


当前回答

编辑:@juanpa。Arrivillaga让我注意到一种更好的方法;参见https://stackoverflow.com/a/63237226

建议你做上面的答案,而不是下面这个。

[旧答案如下,为后人保留]

我❤️保罗的回答

然而,关于类型提示继承与self的关系,有一点需要说明,即如果您通过使用类名作为字符串的文字复制粘贴来键入hint,那么您的类型提示将不会以正确或一致的方式继承。

解决方案是通过在函数本身的返回值上放置类型提示来提供返回类型提示。

✅例如,这样做:

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截图显示类型提示在引用self时正确工作:

❌static_child截图显示类型提示错误地指向父类,即类型提示没有随着继承正确地改变;它是静态的,因为它总是指向父对象,即使它应该指向子对象

其他回答

当基于字符串的类型提示可接受时,也可以使用__qualname__项。它包含类的名称,在类定义的主体中可用。

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

通过这样做,重命名类并不意味着修改类型提示。但我个人并不指望智能代码编辑器能很好地处理这个表单。

从Python 3.11(将于2022年底发布)开始,您将能够使用Self作为返回类型。

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也包含在类型扩展包中(在PyPi上可用),尽管它不是标准库的一部分,但它是类型模块的某种“预览”版本。从https://pypi.org/project/typing-extensions/,

typing_extensions模块有两个相关的用途: 允许使用 旧版本Python上的新类型系统特性。例如, 打字。TypeGuard是Python 3.10中的新功能,但typing_extensions允许 Python 3.6到3.9版本的用户也可以使用它。 使实验 在新类型系统pep被接受并添加到 输入模块。

目前,类型扩展正式支持Python 3.7及更高版本。

编辑:@juanpa。Arrivillaga让我注意到一种更好的方法;参见https://stackoverflow.com/a/63237226

建议你做上面的答案,而不是下面这个。

[旧答案如下,为后人保留]

我❤️保罗的回答

然而,关于类型提示继承与self的关系,有一点需要说明,即如果您通过使用类名作为字符串的文字复制粘贴来键入hint,那么您的类型提示将不会以正确或一致的方式继承。

解决方案是通过在函数本身的返回值上放置类型提示来提供返回类型提示。

✅例如,这样做:

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截图显示类型提示在引用self时正确工作:

❌static_child截图显示类型提示错误地指向父类,即类型提示没有随着继承正确地改变;它是静态的,因为它总是指向父对象,即使它应该指向子对象

将类型指定为字符串很好,但总是让我有点恼火,因为我们基本上是在绕过解析器。所以你最好不要拼错这些字面值字符串:

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

一个轻微的变化是使用绑定类型变量,至少在声明类型变量时你只需要写字符串一次:

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)

如果你只关心修复NameError: name 'Position'没有定义,你可以指定类名为字符串:

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)