要解决这个问题,您需要第二个泛型类型参数,以表示foo 的返回类型。
SelfStr = TypeVar("SelfStr", bound="Container[str, Any]", covariant=True)
到目前为止,一切都很好。让我们定义Container:
class Container(Generic[T, SelfStr]):
def __init__(self, contents: list[T]):
self._contents = contents
def __iter__(self):
return iter(self._contents)
def foo(self) -> SelfStr:
reveal_type(type(self))
# Mypy is wrong here: it thinks that type(self) is already annotated, but in fact the type parameters are erased.
return type(self)([str(x) for x in self]) # type: ignore
请注意,我们必须忽略foo 中的类型。这是因为 mypy 错误地推断出 type(self) 的类型。它认为type(self)返回Container[...](或子类),但实际上它返回Container(或子类)。当我们开始运行这段代码时,您会看到这一点。
接下来,我们需要一些创建容器的方法。我们希望类型看起来像Container[T, Container[str, Container[str, ...]]]。一些✨ magic ✨的时间。
_ContainerStr: TypeAlias = Container[str, "_ContainerStr"]
ContainerComplete: TypeAlias = Container[T, _ContainerStr]
_ContainerStr 别名将为我们提供签名的递归部分。然后我们公开ContainerComplete,我们可以将其用作构造函数,例如:
ContainerComplete[int]([1,2,3])
惊人的!但是子类呢?我们只需要为我们的子类再次做同样的事情:
class SuperContainer(Container[T, SelfStr]):
def time_travel(self):
return "magic"
_SuperContainerStr: TypeAlias = SuperContainer[str, "_SuperContainerStr"]
SuperContainerComplete: TypeAlias = SuperContainer[T, _SuperContainerStr]
全部完成!现在让我们演示一下:
sc = SuperContainerComplete[int]([3, 4, 5])
reveal_type(sc)
sc2 = sc.foo()
reveal_type(sc2)
print(sc2.time_travel())
把所有东西放在一起,我们得到:
from typing import TypeVar, Generic, Any, TypeAlias, TYPE_CHECKING
if not TYPE_CHECKING:
reveal_type = print
T = TypeVar('T')
SelfStr = TypeVar("SelfStr", bound="Container[str, Any]", covariant=True)
class Container(Generic[T, SelfStr]):
def __init__(self, contents: list[T]):
self._contents = contents
def __iter__(self):
return iter(self._contents)
def foo(self) -> SelfStr:
reveal_type(type(self))
# Mypy is wrong here: it thinks that type(self) is already annotated, but in fact the type parameters are erased.
return type(self)([str(x) for x in self]) # type: ignore
def __repr__(self):
return type(self).__name__ + "(" + repr(self._contents) + ")"
_ContainerStr: TypeAlias = Container[str, "_ContainerStr"]
ContainerComplete: TypeAlias = Container[T, _ContainerStr]
class SuperContainer(Container[T, SelfStr]):
def time_travel(self):
return "magic"
_SuperContainerStr: TypeAlias = SuperContainer[str, "_SuperContainerStr"]
SuperContainerComplete: TypeAlias = SuperContainer[T, _SuperContainerStr]
sc = SuperContainerComplete[int]([3, 4, 5])
reveal_type(sc)
sc2 = sc.foo()
reveal_type(sc2)
print(sc2.time_travel())
输出如下所示(您需要最新版本的 mypy):
$ mypy test.py
test.py:17: note: Revealed type is "Type[test.Container[T`1, SelfStr`2]]"
test.py:33: note: Revealed type is "test.SuperContainer[builtins.int, test.SuperContainer[builtins.str, ...]]"
test.py:36: note: Revealed type is "test.SuperContainer[builtins.str, test.SuperContainer[builtins.str, ...]]"
Success: no issues found in 1 source file
$ python test.py
<__main__.SuperContainer object at 0x7f30165582d0>
<class '__main__.SuperContainer'>
<__main__.SuperContainer object at 0x7f3016558390>
magic
$
您可以使用元类删除大量样板。这有一个额外的好处,那就是它是继承的。如果你覆盖__call__,你甚至可以让isinstance正常工作(它不适用于通用类型别名*Complete,它仍然适用于类本身)。