我正在尝试将 blinker 的信号类包装为强制输入的信号类。我试图包装的基本界面是:
class Signal:
def send(self, sender: Any | None, **kwargs):
...
def connect(self, receiver):
...
(这不是确切的界面,但模拟了问题)。
我尝试了几种方法:
我可以使用泛型非常简单地实现
sender
arg 的类型提示:
T = TypeVar("T")
class TypedSignal(Generic[T], Signal):
def send(self, sender: Type[T], **kwargs):
super(TypedSignal, self).send(sender)
def connect(self, receiver: Callable[[Type[T], ...], None]) -> Callable:
return super(TypedSignal, self).connect(receiver)
# used as
my_signal = TypedSignal[MyClass]()
棘手的是当我想为
kwargs
添加类型检查时。我一直在尝试使用的方法是使用可变参数泛型和Unpack
,如下所示:
T = TypeVar("T")
KW = TypeVarTuple("KW")
class TypedSignal(Generic[T, Unpack[KW]], Signal):
def send(self, sender: Type[T], **kwargs: Unpack[Type[KW]]):
super(TypedSignal, self).send(sender)
def connect(self, receiver: Callable[[Type[T], Unpack[Type[KW]]], None]) -> Callable:
return super(TypedSignal, self).connect(receiver)
但是 mypy 抱怨:
error: Unpack item in ** argument must be a TypedDict
这看起来很奇怪,因为即使不使用泛型也会抛出这个错误,更不用说当 TypedDict
被传递时。
P = ParamSpec("P")
class TypedSignal(Generic[P], Signal):
def send(self, *args: P.args, **kwargs: P.kwargs) -> None:
super().send(*args, **kwargs)
def connect(self, receiver: Callable[P, None]):
return super().connect(receiver=receiver)
class Receiver(Protocol):
def __call__(self, sender: MyClass) -> None:
pass
update = TypedSignal[Receiver]()
@update.connect
def my_func(sender: MyClass) -> None:
pass
update.send(MyClass())
但是 mypy 似乎包装了协议,因此它需要一个采用协议的函数,从而出现以下错误:
error: Argument 1 to "connect" of "TypedSignal" has incompatible type "Callable[[MyClass], None]"; expected "Callable[[Receiver], None]" [arg-type]
error: Argument 1 to "send" of "TypedSignal" has incompatible type "MyClass"; expected "Receiver" [arg-type]
有更简单的方法吗?这对于当前的 python 打字来说可能吗?
mypy 版本是 1.9.0 - 尝试使用早期版本,但它完全崩溃了。
我认为这是不可能的。
您可以通过 3 种方式注释
**kwargs
:
**kwargs: Foo
-> kwargs
的类型为 dict[str, Foo]
**kwargs: Unpack[TD]
,其中 TD
是 TypedDict
-> kwargs
的类型为 TD
*args: P.args, **kwargs: P.kwargs
,其中 P
是 ParamSpec
-> kwargs
是通用的 P
因此,拥有泛型
kwargs
的唯一方法是将 1. 与常规 TypeVar
一起使用,这意味着所有关键字参数都需要具有兼容的类型,或者使用 3. 但目前 要求您还拥有 *args
,你没有。