【问题标题】:Can you annotate return type when value is instance of cls?当值是 cls 的实例时,您可以注释返回类型吗?
【发布时间】:2017-01-05 10:36:36
【问题描述】:

给定一个带有辅助方法的类用于初始化:

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方法的返回类型可以注解吗?

clsTrivialClass 我都试过了,但 PyCharm 将它们标记为未解析的引用,这在当时听起来很合理。

【问题讨论】:

    标签: python oop pycharm type-hinting python-typing


    【解决方案1】:

    使用通用类型表示您将返回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(...)
    

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

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

    请参阅 PEP 484 的 Annotating instance and class methods section


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

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

    【讨论】:

    • 是否可以创建对 cls 的前向引用?
    • @Jonatan:不,因为cls 不是类型。
    • @Jonatan:注意TrivialClass 的任何子类也将满足类型提示。
    • 类似的解决方案可以应用于self的类方法:def create_something(self: T) -> T
    • @TomSawyer 我会说这是一个 PyCharm 错误。向他们报告
    【解决方案2】:

    从 Python 3.7 你可以使用__future__.annotations

    from __future__ import annotations
    
    
    class TrivialClass:
        # ...
    
        @classmethod
        def from_int(cls, int_arg: int) -> TrivialClass:
            # ...
            return cls(...)
    
    

    编辑:如果不覆盖 classmethod,你不能继承 TrivialClass,但如果你不需要这个,那么我认为它比前向引用更整洁。

    【讨论】:

    • 与 Noah 的回答相同的问题:您对类方法的返回类型进行了硬编码。
    【解决方案3】:

    对返回类型进行注解的一种简单方法是使用字符串作为类方法返回值的注解:

    # 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
    

    【讨论】:

    • 好多了,因为它不会导致返回 TrivialClass 而不是 'T' 的错误 - 或者如果您将其更改为返回 cls(...) 使用时 cls 无法调用的错误init 构造函数。
    • 你现在不能在不覆盖类方法的情况下继承 TrivialClass。我的第一个版本使用了完全一样的前向引用。
    • 啊,好点子!如果需要子类化,TypeVar 解决方案可能会更好。
    【解决方案4】:

    在 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 月 3 日)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-10
      • 1970-01-01
      • 2017-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多