类方法返回实例的 MyPy 注释

Posted

技术标签:

【中文标题】类方法返回实例的 MyPy 注释【英文标题】:MyPy annotation for classmethod returning instance 【发布时间】:2017-11-22 06:07:31 【问题描述】:

我应该如何注释一个返回cls 实例的@classmethod?这是一个不好的例子:

class Foo(object):
    def __init__(self, bar: str):
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls, bar: str) -> ???:
        return cls(bar + "stuff")

这会返回一个Foo,但更准确地会返回被调用的Foo 的任何子类,因此使用-> "Foo" 进行注释还不够好。

【问题讨论】:

-> 'Foo' 是正确的,这就是您可以(或应该)强制执行的返回值。 mypy.readthedocs.io/en/latest/… 【参考方案1】:

为了完整起见,在 Python 3.7 中,您可以通过在文件开头导入 from __future__ import annotations 来使用 PEP 563 中定义的 postponed evaluation of annotations

那么对于您的代码,它看起来像

from __future__ import annotations

class Foo(object):
    def __init__(self, bar: str):
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls, bar: str) -> Foo:
        return cls(bar + "stuff")

根据docs,此导入将从 Python 3.11 开始有效地自动进行。

【讨论】:

对我来说似乎是一个令人讨厌的惊喜。如果我说-> Foo,那么我希望这个函数返回一个Foo 类型的对象,而不是一个子类。 因为如果你子类化 Foo,那么 classmethod 将返回子类,而不是 Foo,但是类型定义说的是 Foo。 @MaikuMori 这对我来说看起来很自然。子类的实例也是类的实例。您可以使用isinstance 进行实验。这对应于 OO 类中教授的基本内容,您可以使用父类引用来引用子类实例。 这不是 OP 问题的答案。如果某些方法仅存在于子类中而不存在于父类中,则每次都需要返回正确的类型。我敢肯定,很多人都知道如何使用延迟的注释,并且仍然在这个线程上结束,并从@Michael0x2a 的回答中受益(这是我的情况)【参考方案2】:

诀窍是在cls 参数中显式添加注释,结合TypeVar,对于generics,和Type,到represent a class rather than the instance itself,如下所示:

from typing import TypeVar, Type

# Create a generic variable that can be 'Parent', or any subclass.
T = TypeVar('T', bound='Parent')

class Parent:
    def __init__(self, bar: str) -> None:
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls: Type[T], bar: str) -> T:
        # We annotate 'cls' with a typevar so that we can
        # type our return type more precisely
        return cls(bar + "stuff")

class Child(Parent):
    # If you're going to redefine __init__, make sure it
    # has a signature that's compatible with the Parent's __init__,
    # since mypy currently doesn't check for that.

    def child_only(self) -> int:
        return 3

# Mypy correctly infers that p is of type 'Parent',
# and c is of type 'Child'.
p = Parent.with_stuff_appended("10")
c = Child.with_stuff_appended("20")

# We can verify this ourself by using the special 'reveal_type'
# function. Be sure to delete these lines before running your
# code -- this function is something only mypy understands
# (it's meant to help with debugging your types).
reveal_type(p)  # Revealed type is 'test.Parent*'
reveal_type(c)  # Revealed type is 'test.Child*'

# So, these all typecheck
print(p.bar)
print(c.bar)
print(c.child_only())

通常,您可以不加注解cls(和self),但如果您需要引用具体的子类,您可以添加explicit annotation。请注意,此功能仍处于试验阶段,在某些情况下可能存在错误。您可能还需要使用从 Github 克隆的最新版本的 mypy,而不是 pypi 上可用的版本——我不记得该版本是否支持类方法的此功能。

【讨论】:

@taway -- 嗯,很酷!我本可以发誓没有,但我很高兴听到我错了! 如果我希望 with_stuff_appended 返回 Child 的实例怎么办? 这看起来很可怕。 Python 是新的 C++98 吗? :''''( 恕我直言,具有类型检查功能的 Python 在复杂性和上市时间方面可以与 Rust 相提并论。但是mypy救了我很多次,所以绝对是必备工具。 很好的答案!当它被接受时,可能值得一提PEP 673。

以上是关于类方法返回实例的 MyPy 注释的主要内容,如果未能解决你的问题,请参考以下文章

类装饰器上的 Mypy 注释

如何为 Mypy 类型注释指定 OrderedDict K,V 类型?

如何让 Mypy 在 Callable 中识别类的协议成员资格?

从用mypy注释的python函数返回None,多种返回类型

mypy“类模块”注解

mypy:创建一个接受子类实例列表的类型