TL;DR
在当前的类型系统中,您想要做的事情是不可能的.
1.交叉口类型
如果通过修饰器添加到类的属性和方法是静态的(在某种意义上,它们不仅在运行时是已知的),那么您正在描述的实际上是通过混入protocol P
来扩展任何给定的类T
.该协议定义了方法save
等.
要注释这一点,你需要intersection分(满分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.类装饰者问题
目前唯一的解决办法是在您 Select 的类型判断器的相应插件旁边添加一个自定义实现.我只是偶然发现了typing-protocol-intersection
个套餐,它为mypy
做了同样的事情.
如果您安装它并在mypy
配置中添加plugins = typing_protocol_intersection.mypy_plugin
,您可以编写如下代码:
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
但现在我们遇到了下一个问题.使用reveal_type(A.bar())
到mypy
进行测试将产生以下结果:
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
插件背后的人在一起.