Consider the following example class containing attributes that require running a coroutine for initialization:
class Example:
def __init__(self) -> None:
self._connection: Optional[Connection] = None
async def connect() -> None:
self._connection = await connect_somewhere(...)
async def send(data: bytes) -> None:
self._connection.send(data)
If I run mypy (perhaps with strict-optional enabled) on this example, it will complain that _connection
can be None in send
method and the code is not type-safe. I can't initialize the _connection
variable in __init__
, as it needs to be run asynchronously in a coroutine. It's probably a bad idea to declare the variable outside __init__
too. Is there any way to solve this? Or do you recommend another (OOP) design that would solve the issue?
Currently, I either ignore the mypy complaints, prepend assert self._connection
before each usage or append # type: ignore
after the usage.
CodePudding user response:
It is generally not good design to have classes in an unusable state unless some method is called on them. An alternative is dependency injection and an alternative constructor:
from typing import TypeVar, Type
# not strictly needed – one can also use just 'Example'
# if inheritance is not needed
T = TypeVar('T')
class Example:
# class always receives a fully functioning connection
def __init__(self, connection: Connection) -> None:
self._connection = connection
# class can construct itself asynchronously without a connection
@classmethod
async def connect(cls: Type[T]) -> T:
return cls(await connect_somewhere(...))
async def send(data: bytes) -> None:
self._connection.send(data)
This frees __init__
from relying on some other initialiser to be called later on; as a bonus, it is possible to provide a different connection, e.g. for testing.
The alternative constructor, here connect
, still allows to create the object in a self-contained way (without the callee knowing how to connect) but with full async
support.
async def example():
# create instance asynchronously
sender = await Example.connect()
await sender.send(b"Hello ")
await sender.send(b"World!")
CodePudding user response:
It's probably a bad idea to declare the variable outside
__init__
too
This is close. You have to annotate it outside of __init__
.
class Example:
_connection: Connection
async def connect(self) -> None:
self._connection = await connect_somewhere(…)