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

Mic*_*wen 277 python typing pycharm python-3.x 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)
Run Code Online (Sandbox Code Playgroud)

但是我的编辑器(PyCharm)说无法解析引用位置(在_add__方法中).我该如何指定我希望返回类型是类型__add__

编辑:我认为这实际上是一个PyCharm问题.它实际上使用其警告中的信息和代码完成

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

Pau*_*ine 408

TL; DR:如果您使用的是Python 4.0,它就可以了.截至今天(2019年)的3.7+,您必须使用future语句(from __future__ import annotations)来启用此功能- 对于Python 3.6或更低版本,使用字符串.

我猜你有这个例外:

NameError: name 'Position' is not defined
Run Code Online (Sandbox Code Playgroud)

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

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:
        ...
Run Code Online (Sandbox Code Playgroud)

这计划成为Python 4.0中的默认设置.由于Python仍然是动态类型语言,因此在运行时不进行类型检查,因此键入注释应该不会对性能产生影响,对吧?错误!在python 3.7之前,输入模块曾经是核心中最慢的python模块之一,所以如果你升级到3.7时你import typing会看到性能提升7倍.

Python <3.7:使用字符串

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

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...
Run Code Online (Sandbox Code Playgroud)

如果你使用Django框架,这可能是熟悉的,因为Django模型也使用字符串作为前向引用(外键模型已经self或未被声明的外键定义).这应该适用于Pycharm和其他工具.

来源

PEP 484PEP 563的相关部分,为您带来免费旅行:

前向参考

当类型提示包含尚未定义的名称时,该定义可以表示为字符串文字,稍后要解决.

这种情况通常发生的情况是容器类的定义,其中定义的类出现在某些方法的签名中.例如,以下代码(简单二叉树实现的开始)不起作用:

class Tree:
    def __init__(self, left: Tree, right: Tree):
    self.left = left
    self.right = right
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我们写道:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right
Run Code Online (Sandbox Code Playgroud)

字符串文字应该包含一个有效的Python表达式(即,编译(点亮,'','eval')应该是一个有效的代码对象)并且一旦模块完全加载就应该评估没有错误.在其中计算它的本地和全局命名空间应该是相同的命名空间,在该命名空间中将评估同一函数的默认参数.

和PEP 563:

在Python 4.0中,函数和变量注释将不再在定义时进行评估.相反,字符串形式将保留在相应的__annotations__字典中.静态类型检查器将看不到行为上的差异,而在运行时使用注释的工具将不得不执行推迟的评估.

...

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

from __future__ import annotations
Run Code Online (Sandbox Code Playgroud)

你可能想要做的事情

A.定义一个假人 Position

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

class Position(object):
    pass


class Position(object):
    ...
Run Code Online (Sandbox Code Playgroud)

这将摆脱,NameError甚至看起来可以:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}
Run Code Online (Sandbox Code Playgroud)

但是吗?

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

B.猴子补丁以添加注释:

您可能想尝试一些Python元编程魔术并编写装饰器来修补类定义以添加注释:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)
Run Code Online (Sandbox Code Playgroud)

装饰者应该负责相当于:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position
Run Code Online (Sandbox Code Playgroud)

至少看起来是对的:

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

可能太麻烦了.

结论

如果您使用的是3.6或更低版本,请使用包含类名的字符串文字,在3.7中使用from __future__ import annotations它就可以了.

  • 对于任何使用“from __future__ import 注解”的人来说,重要的一点是——它必须在所有其他导入之前导入。 (16认同)
  • Python 3.11 引入了 `Self` 注释。https://docs.python.org/3.11/whatsnew/3.11.html#whatsnew311-pep673 (9认同)
  • &gt;如果您使用的是Python 4.0,那么顺便说一句,您见过Sarah Connor吗?:) (6认同)
  • 有没有办法指定函数的返回类型是当前类,无论它是什么?例如,“@classmethod def f(cls) -&gt; CurrentClass:”,其中“CurrentClass”的计算结果为运行时的“cls”?那么,如果“A”和“B”继承自实现“f”的类,那么“Af() -&gt; A”和“Bf() -&gt; B”呢? (6认同)
  • 来自 PEP673:“fromtypesimportSelf”将来可能会使这变得更容易(似乎是 PY3.11 功能) (6认同)
  • 是的,这不是 PyCharm 问题,而是 Python 3.5 PEP 484 问题。我怀疑如果您通过 mypy 类型工具运行它,您会收到相同的警告。 (4认同)

che*_*ner 201

在Python 3.11中实现的PEP 673添加了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 type(self)(self.x + other.x, self.y + other.y)
Run Code Online (Sandbox Code Playgroud)

返回Self通常是一个好主意,但是您必须返回与 相同类型的对象self,这意味着调用type(self)而不是Position


对于旧版本的 Python(当前为 3.7 及更高版本),请使用该typing-extensions包。其目的之一是

允许在较旧的 Python 版本上使用新类型系统功能。例如,typing.TypeGuard它是 Python 3.10 中的新功能,但typing_extensions也允许使用早期 Python 版本的用户使用它。

然后你只需导入 from而typing_extensions不是typing例如from typing_extensions import Self

  • 在 Python 3.11 中,这个解决方案变得最不笨拙且最简洁。 (8认同)
  • 我相信它已经作为 `typing_extensions` 的一部分提供,但 `mypy` 还不理解它。Python 3.11 跟踪问题可在此处找到:https://github.com/python/mypy/issues/12840#issue-1244203018 (6认同)
  • 不。“__future__”更多的是让破坏性语法功能现在选择加入,然后在未来版本中成为必需的。(这并不是说第三方库现在无法提供它,但它不会成为现有 Python 版本中标准库的一部分。) (2认同)
  • **注意** 这与使用“from __future__ import comments”和使用“Position”进行注释不同,其中子类“SubPosition”上的“__add__”接受并返回“Position”。对于“Self”,它需要并返回一个“SubPosition”。两种方法都可以是正确的,这取决于具体的用例 (2认同)

Yvo*_*PIS 16

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

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()
Run Code Online (Sandbox Code Playgroud)

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

  • 这特别有用,因为它不会对类名进行硬编码,因此它可以在子类中继续工作。 (5认同)
  • 请注意,就“mypy”而言,这不是有效的注释。 (3认同)

jsb*_*eno 12

在解析类主体本身时,"位置"这个名称是不可用的.我不知道你是如何使用类型声明的,但Python的PEP 484 - 这是大多数模式应该使用的,如果使用这些打字提示说你可以简单地将名称作为字符串放在这一点:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)
Run Code Online (Sandbox Code Playgroud)

检查https://www.python.org/dev/peps/pep-0484/#forward-references - 符合的工具将知道从那里打开类名并使用它.(总是很重要的是记住,Python语言本身并没有对这些注释做任何事情 - 它们通常用于静态代码分析,或者可以在运行时具有用于类型检查的库/框架 - 但是你必须明确地设置它)


vbr*_*aun 9

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

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)
Run Code Online (Sandbox Code Playgroud)

稍微有点变化就是使用绑定的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)
Run Code Online (Sandbox Code Playgroud)

  • `typing.Self` 将在 Python 3.11 中可用(根据 [PEP-673](https://www.python.org/dev/peps/pep-0673/))。 (7认同)
  • 我希望Python有一个“ typing.Self”来明确地指定它。 (4认同)
  • 此外,注释说返回类型取决于“other”,但很可能它实际上取决于“self”。因此,您需要在“self”上添加注释来描述正确的行为(也许“other”应该只是“Position”,以表明它与返回类型无关)。这也可以用于仅使用“self”的情况。例如 `def __aenter__(self: T) -&gt; T:` (3认同)
  • 我来这里是想看看是否存在像你的 `typing.Self` 这样的东西。在利用多态性时,返回硬编码字符串无法返回正确的类型。就我而言,我想实现一个 **deserialize** 类方法。我决定返回一个 dict (kwargs) 并调用 `some_class(**some_class.deserialize(raw_data))`。 (2认同)

Mac*_*eek 9

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

def __add__(self, other: 'Position') -> 'Position':
Run Code Online (Sandbox Code Playgroud)

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

from __future__ import annotations
Run Code Online (Sandbox Code Playgroud)

但是,如果您还希望它适用于子类,并返回特定的子类,则需要使用一个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)
Run Code Online (Sandbox Code Playgroud)

  • `T = TypeVar('T',bound=Position)` 在定义之前不是引用了 `Position` 吗? (4认同)
  • 是否有任何聪明的技巧来拥有可以重用的通用“Self”? (2认同)

use*_*679 8

一世 ??保罗的回答

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

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

? 例如,执行以下操作:

class DynamicParent:
  def func(self):
    # roundabout way of returning self in order to have inherited type hints of the return
    # /sf/answers/4545728491/
    _self:self.__class__ = self
    return _self
Run Code Online (Sandbox Code Playgroud)

? 而不是这样做:

class StaticParent:
  def func(self) -> 'StaticParent':
    return self
Run Code Online (Sandbox Code Playgroud)

以下是您想通过回旋处进行类型提示的原因?上面显示的方式

class StaticChild(StaticParent):
  pass

class DynamicChild(DynamicParent):
  pass

static_child = StaticChild()
dynamic_child = DynamicChild()
Run Code Online (Sandbox Code Playgroud)

? dynamic_child屏幕截图显示在引用 self 时类型提示工作正常:

在此处输入图片说明

? static_child截图显示类型提示错误地指向父类,即类型提示没有随着继承正确改变;这是static因为它总是指向父母,即使它应该指向孩子

在此处输入图片说明

  • 这不是有效的类型注释,也不是对您想要表达的内容进行类型注释的正确方法,应该使用绑定到父类的类型变量进行注释 (2认同)
  • 请参阅:/sf/answers/4426605851/ (2认同)