如何指定方法的返回类型与python中的类本身相同?

时间:2015-11-04 22:17:54

标签: python python-3.x pycharm typing python-3.5

我在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问题。它实际上使用了警告中的信息和代码完成

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

6 个答案:

答案 0 :(得分:281)

TL; DR :如果您使用的是Python 4.0,它就可以正常运行。截至今天(2019年)的3.7+,你必须使用未来的声明(from __future__ import annotations)来启用此功能 - 对于Python 3.6或更低版本,使用字符串。

我猜你有这个例外:

NameError: name 'Position' is not defined

这是因为除非您使用的是Python 4,否则必须先定义Position才能在注释中使用它。

Python 3.7+:from __future__ import annotations

Python 3.7引入了PEP 563: postponed evaluation of annotations。使用future语句from __future__ import annotations的模块将自动将注释存储为字符串:

from __future__ import annotations

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

这计划成为Python 4.0的默认设置。由于Python仍然是动态类型语言,因此在运行时不进行类型检查,因此键入注释应该不会对性能产生影响,对吧?错误!在python 3.7之前,输入模块曾经是one of the slowest python modules in core所以如果你import typing,当你升级到3.7时,你会看到up to 7 times increase in performance

Python< 3.7:使用字符串

According to 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表达式(即,编译(点亮,'' eval')应该是一个有效的代码对象)并且应该评估没有错误一旦模块满载。在其中计算它的本地和全局命名空间应该是相同的命名空间,其中将评估同一函数的默认参数。

和PEP 563:

  

在Python 4.0中,将不再在定义时评估函数和变量注释。相反,字符串形式将保留在相应的__annotations__字典中。静态类型检查器的行为没有区别,而在运行时使用注释的工具则必须执行推迟的评估。

     

...

     

可以使用以下特殊导入从Python 3.7开始启用上述功能:

from __future__ import annotations

您可能想要做的事情

甲。定义一个虚拟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元编程魔术并编写装饰器 修补类定义以添加注释:

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它就可以了。

答案 1 :(得分:11)

名称'职位'在解析类体本身时是不可用的。我不知道你是如何使用类型声明的,但是Python的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 - 符合要求的工具将知道从那里打开类名并使用它。(记住Python语言本身不做这些注释总是很重要的 - 它们通常用于静态代码分析,或者可以在运行时使用库/框架进行类型检查 - 但是你必须明确地设置它)

答案 2 :(得分:7)

将类型指定为字符串很好,但总是给我一点点,我们基本上绕过了解析器。所以你最好不要拼错这些文字字符串中的任何一个:

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)

答案 3 :(得分:5)

如果只关心修复NameError: name 'Position' is not defined,则可以将类名指定为字符串:

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

或者,如果您使用的是Python 3.7或更高版本,请将以下行添加到代码顶部(恰好在其他导入之前)

from __future__ import annotations

但是,如果您还希望它适用于子类并返回特定的子类,则需要通过定义Generic使用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)

答案 4 :(得分:2)

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

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

这样做,重命名类并不意味着修改类型提示。但是我个人并不希望智能代码编辑器能够很好地处理这种形式。

答案 5 :(得分:0)

我❤️Paulo's answer

但是,与自身有关的类型提示继承有一个要点,即如果您通过使用类名的文字复制粘贴作为字符串来键入提示,则您的类型提示将不会继承正确或一致的方式。

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

✅例如,执行以下操作:

class DynamicParent:
  def func(self):
    # roundabout way of returning self in order to have inherited type hints of the return
    this:self.__class__ = self
    return this

代替这样做:

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

下面是您要通过上面显示的回旋处do进行类型提示的原因

class StaticChild(StaticParent):
  pass

class DynamicChild(DynamicParent):
  pass

static_child = StaticChild()
dynamic_child = DynamicChild()

dynamic_child屏幕截图显示,引用自身时,类型提示正常工作:

enter image description here

static_child屏幕快照显示类型提示错误地指向父类,即类型提示不能随着继承正确更改;之所以是static,是因为它始终会指向父级,即使它应该指向子级也是如此

enter image description here