我试图将我们在代码库中使用的模式提取到更通用,可重用的结构中。但是,我似乎无法使通用类型注释与mypy一起使用。
这就是我得到的:
from abc import (
ABC,
abstractmethod
)
import asyncio
import contextlib
from typing import (
Any,
Iterator,
Generic,
TypeVar
)
_TMsg = TypeVar('_TMsg')
class MsgQueueExposer(ABC, Generic[_TMsg]):
@abstractmethod
def subscribe(self, subscriber: 'MsgQueueSubscriber[_TMsg]') -> None:
raise NotImplementedError("Must be implemented by subclasses")
@abstractmethod
def unsubscribe(self, subscriber: 'MsgQueueSubscriber[_TMsg]') -> None:
raise NotImplementedError("Must be implemented by subclasses")
class MsgQueueSubscriber(Generic[_TMsg]):
@contextlib.contextmanager
def subscribe(
self,
msg_queue_exposer: MsgQueueExposer[_TMsg]) -> Iterator[None]:
msg_queue_exposer.subscribe(self)
try:
yield
finally:
msg_queue_exposer.unsubscribe(self)
class DemoMsgQueSubscriber(MsgQueueSubscriber[int]):
pass
class DemoMsgQueueExposer(MsgQueueExposer[int]):
# The following works for mypy:
# def subscribe(self, subscriber: MsgQueueSubscriber[int]) -> None:
# pass
# def unsubscribe(self, subscriber: MsgQueueSubscriber[int]) -> None:
# pass
# This doesn't work but I want it to work :)
def subscribe(self, subscriber: DemoMsgQueSubscriber) -> None:
pass
def unsubscribe(self, subscriber: DemoMsgQueSubscriber) -> None:
pass
我注释掉了一些有效的代码,但并不能完全满足我的需求。基本上,我希望DemoMsgQueueExposer
在其DemoMsgQueSubscriber
和subscribe
方法中接受unsubscribe
。如果我使用MsgQueueSubscriber[int]
作为类型,但我希望它接受该类型的子类型,则代码类型检查就很好。
我一直遇到以下错误。
generic_msg_queue.py:55: error: Argument 1 of "subscribe" incompatible with supertype "MsgQueueExposer"
我觉得这与协/反变量有关,但是在放弃并来到这里之前,我尝试了几件事。
答案 0 :(得分:3)
您最好的选择是1)仅将subscribe
中的unsubscribe
和MsgQueueExposer
从MsgQueueExposer
中删除,或2)使msg
对于订阅者而言是通用的,可以代替_TMsg
。
这里是一个示例2的示例,假设我们要保留messages()
类型的参数。请注意,出于演示目的,我添加了from abc import ABC, abstractmethod
import asyncio
import contextlib
from typing import Any, Iterator, Generic, TypeVar, List
_TMsg = TypeVar('_TMsg')
_TSubscriber = TypeVar('_TSubscriber', bound='MsgQueueSubscriber')
class MsgQueueExposer(ABC, Generic[_TSubscriber, _TMsg]):
@abstractmethod
def subscribe(self, subscriber: _TSubscriber) -> None:
raise NotImplementedError("Must be implemented by subclasses")
@abstractmethod
def unsubscribe(self, subscriber: _TSubscriber) -> None:
raise NotImplementedError("Must be implemented by subclasses")
@abstractmethod
def messages(self) -> List[_TMsg]:
raise NotImplementedError("Must be implemented by subclasses")
class MsgQueueSubscriber(Generic[_TMsg]):
# Note that we are annotating the 'self' parameter here, so we can
# capture the subclass's exact type.
@contextlib.contextmanager
def subscribe(
self: _TSubscriber,
msg_queue_exposer: MsgQueueExposer[_TSubscriber, _TMsg]) -> Iterator[None]:
msg_queue_exposer.subscribe(self)
try:
yield
finally:
msg_queue_exposer.unsubscribe(self)
class DemoMsgQueSubscriber(MsgQueueSubscriber[int]):
pass
class DemoMsgQueueExposer(MsgQueueExposer[DemoMsgQueSubscriber, int]):
def subscribe(self, subscriber: DemoMsgQueSubscriber) -> None:
pass
def unsubscribe(self, subscriber: DemoMsgQueSubscriber) -> None:
pass
def messages(self) -> List[int]:
pass
方法:
MsgQueueExposer
更广泛地讲,我们想表达每个MsgQueueExposer
仅适用于特定类型的订户的想法,因此我们需要将该信息编码到某个地方。
其中一个漏洞是,mypy无法确保在使用class DemoMsgQueSubscriber(MsgQueueSubscriber[str])
时,订户接收到的任何类型以及暴露者期望的任何类型的协议都是一致的。因此,如果我们将演示订户定义为DemoMsgQueueExposer
,但保持list1 = [2, 3, 6, 7, 14, 21, 23, 42, 46, 69, 138, 161, 322, 483]
不变,则mypy将无法检测到此错误。
但是我假设您将总是成对地创建一个新的订户和一个新的暴露者,并且可以仔细地进行审核,因此在实践中不太可能发生此错误。