当值是 cls 的实例时,您可以注释 return 类型吗?

Can you annotate return type when value is instance of cls?

给定一个 class 和一个用于初始化的辅助方法:

class TrivialClass:
    def __init__(self, str_arg: str):
        self.string_attribute = str_arg

    @classmethod
    def from_int(cls, int_arg: int) -> ?:
        str_arg = str(int_arg)
        return cls(str_arg)

是否可以注释from_int方法的return类型?

我已经尝试了 clsTrivialClass 但 PyCharm 将它们标记为未解决的引用,这在当时听起来是合理的。

使用通用类型表示您将return创建cls的实例:

from typing import Type, TypeVar

T = TypeVar('T', bound='TrivialClass')

class TrivialClass:
    # ...

    @classmethod
    def from_int(cls: Type[T], int_arg: int) -> T:
        # ...
        return cls(...)

任何子class覆盖class方法,然后returning 父class的实例(TrivialClass 或仍然是祖先的子 class)将被检测为错误,因为工厂方法被定义为 returning 类型 cls 的实例。

bound 参数指定 T 必须是 TrivialClass 的(subclass);因为在定义泛型时 class 尚不存在,所以您需要使用 forward reference (带有名称的字符串)。

参见 PEP 484 的 Annotating instance and class methods section


注:本回答第一次修改提倡使用前向引用 将 class 本身命名为 return 值,但是 issue 1212 使得使用泛型成为可能,这是一个更好的解决方案。

从 Python 3.7 开始,当您使用 from __future__ import annotations 启动模块时,您 可以 避免在注释中使用前向引用,但创建一个 TypeVar() 模块级别的对象不是注解。即使在 Python 3.10 中也是如此,它推迟了注释中的所有类型提示解析。

注解return类型的一种简单方法是使用字符串作为class方法的return值的注解:

# test.py
class TrivialClass:
  def __init__(self, str_arg: str) -> None:
    self.string_attribute = str_arg

  @classmethod
  def from_int(cls, int_arg: int) -> 'TrivialClass':
    str_arg = str(int_arg)
    return cls(str_arg)

这通过了 mypy 0.560 并且没有来自 python 的错误:

$ mypy test.py --disallow-untyped-defs --disallow-untyped-calls
$ python test.py

从 Python 3.7 开始你可以使用 __future__.annotations:

from __future__ import annotations


class TrivialClass:
    # ...

    @classmethod
    def from_int(cls, int_arg: int) -> TrivialClass:
        # ...
        return cls(...)

编辑:您不能在不覆盖类方法的情况下对 TrivialClass 进行子类化,但如果您不需要它,那么我认为它比前向引用更整洁。

在 Python 3.11 中,使用新的 Self 类型将有更好的方法:

class TrivialClass:
    def __init__(self, str_arg: str):
        self.string_attribute = str_arg

    @classmethod
    def from_int(cls, int_arg: int) -> Self:
        str_arg = str(int_arg)
        return cls(str_arg)

这也适用于子 类。

这在 PEP 673 中有描述。 Use in Classmethod Signatures 部分中使用的示例完全等同于此问题。

我打算在 Python 3.11 发布后(2022-10-03 截至目前)接受这个答案。