类型暗示返回 self 的类方法的返回值?

Kan*_*shk 9 python python-typing

正如问题所描述的,我想输入提示selfreturn ,例如:

class A:
    def foo(self) -> [what goes here?]:
        # do something
        return self
Run Code Online (Sandbox Code Playgroud)

我已经尝试过的事情:

  1. 将其注释为Afrom __future__ import annotations在顶部添加):这意味着该方法返回一个实例化A()对象,但不一定self
  2. 将其注释为Type[A](adding from typing import Type):这意味着该方法返回的是返回一个未实例化的A,它与 相差甚远self
  3. 注释为Self(添加from typing_extensions import Self):mypy 给出错误: Variable "typing_extensions.Self" is not valid as a type [valid-type]mypy(error)

可能有帮助的事情foo:将鼠标悬停在没有返回值注释的方法上,VScode提示显示 - Self@A,我不明白,但是,这绝对区分返回另一个实例化类A()和返回self...谢谢

STe*_*kov 8

我找不到任何详细涵盖这一点的问题,因此将尝试解释。

对于类型检查来说“相同类型”意味着什么

好吧,这可能是“你不能,也不应该”的长形式。

类型检查的目的是确认所有函数都使用正确的参数类型调用并返回预期的类型。我建议先阅读PEP483以更好地理解类型的概念。假设您有以下情况:

s1 = ''.join(['a', 'b', 'c'])
s2 = ''.join(['a', 'b', 'c'])
assert s1 is not s2
assert s1 == s2
Run Code Online (Sandbox Code Playgroud)

join为了避免优化,但这是另一个故事)。它们是同一个对象吗?不,is not明确说明了这一点(它们具有不同的内存地址)。但s2只要你愿意就可以接受吗s1?绝对是的。您不会创建一个仅对 进行操作s1并使用 来检查这一事实的函数is,对吧?

现在作为对精确对象的引用和作为任何实例的引用有什么区别?当我们谈论类型检查时,所有实例都是完全等效且无法区分的。它们具有相同的方法和属性集(包括类型)。我们可以问:“如果我们显式地将对象声明为实例而不仅仅是类型,会引入或删除哪些类型错误?” 我实在想不出任何一个。如果您希望在语义上使用此方法,请使用文档字符串 - 类型不应被滥用于所有内容。对象与类型检查器的任何其他实例完全相同。selfselfAAselfselfselfA()

简单的解决方案:返回类实例

您的第一个代码示例几乎没问题。注释 return 来A告诉它返回 class 的实例A,它将适用于最终类:

class A:
    def foo(self) -> A:
        return self
Run Code Online (Sandbox Code Playgroud)

然而,这种方法有一个缺点(关于类型,PEP673 中有很好的解释Self):

class AChild(A):
    pass

# mypy
reveal_type(AChild().foo())  # N: revealed type is "__main__.A"
Run Code Online (Sandbox Code Playgroud)

如果您创建新Afoo并返回它,那么这种方法是完美的。如果你return self- 它是有效的,但不精确。这就是为什么我们需要Self类型。

Self类型

Self类型是在PEP673mypy中引入的,在撰写本文时尚未得到支持。(更新:自mypy 1.02023 年 2 月 6 日发布以来受支持)您在第三个示例中的用法完全有效,并且在类型检查器中实现后将起作用(请参阅 PEP 的“动机”部分中的第 5 个代码块)。该支持已添加到mypy版本中1.0,因此如果您使用此版本或更高版本 - 本节是正确的答案。

以下是您可以如何使用Self(假设python=3.11不打扰typing_extensions):

from typing import Self

class A:
    def foo(self) -> Self:
        return self

class AChild(A):
    pass

# mypy
reveal_type(AChild().foo())  # N: revealed type is "__main__.AChild"
reveal_type(A().foo())  # N: revealed type is "__main__.A"
Run Code Online (Sandbox Code Playgroud)

实现Self类型而不使用它

但是,您可以Self通过几行代码(以及python >= 3.7AFAIR)准确地模仿。

from typing import TypeVar

_Self = TypeVar('_Self', bound='A')

class A:
    def foo(self: _Self) -> _Self:
        return self

class AChild(A):
    pass

# mypy
reveal_type(AChild().foo())  # N: revealed type is "__main__.AChild"
reveal_type(A().foo())  # N: revealed type is "__main__.A"
Run Code Online (Sandbox Code Playgroud)

现在这有效了。所有子类都会返回它们的类实例。