类方法返回实例的类型注释

返回 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"进行注释不够好。

22868 次浏览

诀窍是将一个注释显式地添加到 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(和 self)不带注释,但是如果您需要引用特定的子类,您可以添加一个 明确的注释。请注意,这个特性仍处于试验阶段,在某些情况下可能存在 bug。您可能还需要使用从 Github 克隆的 my 的最新版本,而不是在 pypi 上可用的版本——我不记得那个版本是否支持类方法的这个特性。

为了完整起见,在 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")

根据 医生,从 Python 3.11开始,这种导入将实际上是自动的。