长话短说
当前类型系统无法执行您尝试执行的操作。
1.路口类型
如果您通过装饰器添加到类中的属性和方法是静态的(从某种意义上说,它们不仅在运行时已知),那么您所描述的实际上是任何给定类的扩展 T 通过混合protocolP。该协议定义了方法save 等等。
要对此进行注释,您需要一个路口来自T & P。它看起来像这样:
from typing import Protocol, TypeVar
T = TypeVar("T")
class P(Protocol):
@staticmethod
def bar() -> str: ...
def dec(cls: type[T]) -> type[Intersection[T, P]]:
setattr(cls, "bar", lambda: "x")
return cls # type: ignore[return-value]
@dec
class A:
@staticmethod
def foo() -> int:
return 1
您可能会注意到 Intersection 的导入明显缺失。这是因为尽管它是 Python 类型系统的 most requested features 之一,但直到今天它仍然缺失。目前还没有办法在 Python 类型中表达这个概念。
2.类装饰器问题
目前唯一的解决方法是自定义实现以及您选择的类型检查器的相应插件。我刚刚偶然发现了 typing-protocol-intersection 包,它就是为 mypy 做的。
如果您安装它并将 plugins = typing_protocol_intersection.mypy_plugin 添加到您的 mypy 配置中,您可以像这样编写代码:
from typing import Protocol, TypeVar
from typing_protocol_intersection import ProtocolIntersection
T = TypeVar("T")
class P(Protocol):
@staticmethod
def bar() -> str: ...
def dec(cls: type[T]) -> type[ProtocolIntersection[T, P]]:
setattr(cls, "bar", lambda: "x")
return cls # type: ignore[return-value]
@dec
class A:
@staticmethod
def foo() -> int:
return 1
但是在这里我们遇到了下一个问题。通过 mypy 使用 reveal_type(A.bar()) 进行测试将产生以下结果:
error: "Type[A]" has no attribute "bar" [attr-defined]
note: Revealed type is "Any"
然而,如果我们这样做:
class A:
@staticmethod
def foo() -> int:
return 1
B = dec(A)
reveal_type(B.bar())
我们没有收到来自mypy 和note: Revealed type is "builtins.str" 的投诉。虽然我们之前做的是等价的!
这不是插件的错误,而是mypy 内部的错误。这是另一个long-standing issue,mypy 没有正确处理类装饰器。
DIY
换句话说,你只需要等到这两个漏洞被修补。或者您可以希望至少 mypy 的装饰器问题很快得到修复,同时为交集类型编写您自己的 VSCode 插件。也许你可以和我上面提到的mypy插件背后的人聚在一起。