python—如何使用封闭类的类型键入提示方法?

roejwanj  于 2021-08-20  发布在  Java
关注(0)|答案(6)|浏览(342)

我在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 ?
编辑:我认为这实际上是一个问题。它实际上在警告和代码完成中使用了这些信息

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

vmpqdwk3

vmpqdwk31#

tl;dr:如果您使用的是python3.10或更高版本,它就可以正常工作。从今天(2019年)起,在3.7+中,您必须使用future语句启用此功能( from __future__ import annotations ). 在Python3.6或更低版本中,使用字符串。
我猜你得到了这个例外:

NameError: name 'Position' is not defined

这是因为 Position 除非您使用的是python 3.10或更高版本,否则必须先定义,然后才能在注解中使用它。

Python3.7+:来自未来导入注解

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仍然是一种动态类型化语言,所以在运行时不会进行类型检查,所以类型注解应该不会对性能产生影响,对吗?错了!在Python3.7之前,类型模块曾经是内核中速度最慢的python模块之一,因此如果 import typing 升级到3.7后,性能将提高7倍。

python<3.7:使用字符串

根据pep 484,您应该使用字符串而不是类本身:

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

如果您使用django框架,这可能很熟悉,因为django模型也使用字符串作为正向引用(外键定义,其中外键定义是 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”)应该是一个有效的代码对象,并且在模块完全加载后,它应该无错误地进行计算。在其中求值的本地和全局名称空间应该是相同的名称空间,相同函数的默认参数将在其中求值。
和政治公众人物563:

实施

在Python3.10中,函数和变量注解将不再在定义时进行计算。相反,字符串形式将保留在相应的 __annotations__ 字典。静态类型检查器在行为上看不到任何差异,而在运行时使用注解的工具将不得不执行延迟的计算。
...

在python 3.7中启用未来行为

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

from __future__ import annotations

你可能想做的事情

a。定义一个虚拟位置

在类定义之前,放置一个虚拟定义:

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。要添加注解,请执行以下操作:

您可能希望尝试一些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

可能太麻烦了。

monwx1rj

monwx1rj2#

将类型指定为string很好,但总让我有点恼火,因为我们基本上是在绕过解析器。因此,您最好不要拼写这些文字字符串中的任何一个:

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)
ewm0tg9j

ewm0tg9j3#

在解析类主体本身时,名称“position”不可用。我不知道您是如何使用类型声明的,但是python的pep 484——如果使用这些类型提示,大多数模式都应该使用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-引用—符合该要求的工具将知道如何从那里打开类名并使用它。(请务必记住,python语言本身并不做这些注解—它们通常用于静态代码分析,或者可以有一个用于在运行时进行类型检查的库/框架,但必须显式地进行设置)。
同时更新,从python 3.7开始,检查pep-563-从python 3.8开始,可以编写 from __future__ import annotations 为了推迟注解的评估,前向引用类应该可以直接工作。

mdfafbf1

mdfafbf14#

如果可以接受基于字符串的类型提示,则 __qualname__ 项目也可以使用。它保存类的名称,并且在类定义的主体中可用。

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

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

o8x7eapl

o8x7eapl5#

如果你只关心修理机器的话 NameError: name 'Position' is not defined ,可以将类名指定为字符串:

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

或者,如果您使用Python3.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)
u91tlkcl

u91tlkcl6#

我❤️ 保罗的回答
然而,关于类型提示继承与self的关系有一点需要说明,即如果您使用类名的文本副本粘贴作为字符串来输入提示,那么您的类型提示将不会以正确或一致的方式继承。
解决方法是通过将类型提示放在函数本身的返回上来提供返回类型提示。
✅ 例如,请执行以下操作:

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 屏幕截图显示类型提示错误地指向父类,即类型提示没有随着继承而正确更改;它是 static 因为它总是指向父对象,即使它应该指向子对象

相关问题