使Mypy接受泛型的子类型作为方法参数

时间:2018-06-26 09:33:15

标签: python generics mypy

我试图将我们在代码库中使用的模式提取到更通用,可重用的结构中。但是,我似乎无法使通用类型注释与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在其DemoMsgQueSubscribersubscribe方法中接受unsubscribe。如果我使用MsgQueueSubscriber[int]作为类型,但我希望它接受该类型的子类型,则代码类型检查就很好。

我一直遇到以下错误。

generic_msg_queue.py:55: error: Argument 1 of "subscribe" incompatible with supertype "MsgQueueExposer"

我觉得这与协/反变量有关,但是在放弃并来到这里之前,我尝试了几件事。

1 个答案:

答案 0 :(得分:3)

您最好的选择是1)仅将subscribe中的unsubscribeMsgQueueExposerMsgQueueExposer中删除,或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将无法检测到此错误。

但是我假设您将总是成对地创建一个新的订户和一个新的暴露者,并且可以仔细地进行审核,因此在实践中不太可能发生此错误。