59

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

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"还不够好。

4

2 回答 2

78

诀窍是显式地为cls参数添加注释,结合TypeVar, 用于泛型, 和Type, 来表示一个类而不是实例本身,如下所示:

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(and self),但是如果你需要引用特定的子类,你可以添加一个显式的注解。请注意,此功能仍处于试验阶段,在某些情况下可能存在错误。您可能还需要使用从 Github 克隆的最新版本的 mypy,而不是 pypi 上可用的版本——我不记得该版本是否支持类方法的此功能。

于 2017-06-20T05:34:20.093 回答
40

为了完整起见,在 Python 3.7 中,您可以通过在文件开头导入来使用PEP 563postponed evaluation of annotations中定义的 。from __future__ import 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")

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

于 2018-11-23T16:45:32.913 回答