Ответ 1
Ваши лучшие ставки заключаются в том, чтобы: 1) просто удалить subscribe
и unsubscribe
от unsubscribe
от MsgQueueExposer
вообще или 2) сделать MsgQueueExposer
общим для абонента, либо в дополнение, либо вместо msg
.
Ниже приведен пример того, как может выглядеть подход 2, предполагая, что мы хотим сохранить параметр типа _TMsg
. Обратите внимание, что я добавил метод 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 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
работает только для определенного типа подписчика, поэтому нам нужно было где-то закодировать эту информацию.
Одно отверстие в этом заключается в том, что mypy не сможет убедиться, что когда вы используете MsgQueueExposer
что независимо от того, какой тип получает абонент, и что независимо от того, какой тип ожидающий будет ожидать, согласятся. Итак, если мы определили демо-подписчика как class DemoMsgQueSubscriber(MsgQueueSubscriber[str])
но сохранили DemoMsgQueueExposer
же, mypy не смог бы обнаружить эту ошибку.
Но я предполагаю, что вы всегда будете создавать новый подписчик и новый экспонент в парах и это то, что вы можете тщательно проверить, так что эта ошибка, вероятно, вряд ли произойдет на практике.