如何正确输入 asyncio class 实例变量
How to correctly type an asyncio class instance variables
考虑以下示例 class,其中包含需要 运行 协程进行初始化的属性:
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)
如果我在这个例子中 运行 mypy(可能启用了严格可选),它会抱怨 _connection
可以是 None 在 send
方法和代码不是类型安全的。我无法在 __init__
中初始化 _connection
变量,因为它需要在协程中异步 运行。在 __init__
之外声明变量也可能不是一个好主意。有什么办法可以解决这个问题吗?或者您推荐另一种 (OOP) 设计来解决这个问题?
目前,我要么忽略 mypy 的投诉,要么在每次使用前添加 assert self._connection
,要么在使用后添加 # type: ignore
。
It's probably a bad idea to declare the variable outside __init__
too
这很接近。您必须在 __init__
.
之外对其进行注释
class Example:
_connection: Connection
async def connect(self) -> None:
self._connection = await connect_somewhere(…)
让 类 处于不可用状态通常不是好的设计,除非对它们调用某些方法。替代方案是 dependency injection 和替代构造函数:
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(self, data: bytes) -> None:
self._connection.send(data)
这使 __init__
不再依赖于稍后调用的其他初始化程序;作为奖励,可以提供不同的连接,例如用于测试。
此处 connect
的替代构造函数仍然允许以自包含的方式创建对象(被调用者不知道如何连接)但具有完整的 async
支持。
async def example():
# create instance asynchronously
sender = await Example.connect()
await sender.send(b"Hello ")
await sender.send(b"World!")
要获得打开和关闭的完整生命周期,支持async with
是最直接的方法。这可以通过与替代构造函数类似的方式得到支持——通过提供替代构造 作为上下文管理器:
from typing import TypeVar, Type, AsyncIterable
from contextlib import asynccontextmanager
T = TypeVar('T')
class Example:
def __init__(self, connection: Connection) -> None:
self._connection = connection
@asynccontextmanager
@classmethod
async def scope(cls: Type[T]) -> AsyncIterable[T]:
connection = await connect_somewhere(...) # use `async with` if possible!
try:
yield cls(connection)
finally:
connection.close()
async def send(self, data: bytes) -> None:
self._connection.send(data)
替代connect
为简洁起见省略了构造函数。对于 Python 3.6,asynccontextmanager
可以从 the asyncstdlib
获取(免责声明:我维护这个库)。
有一个普遍的警告:关闭确实会使对象处于无法使用的状态 - 因此不一致 - 实际上根据定义。 Python 的类型系统无法将“打开 Connection
” 与“关闭 Connection
” 分开,尤其是无法检测到 .close
或上下文转换的结束从一个到另一个。
通过使用 async with
可以部分回避这个问题,因为上下文管理器通常被认为在按照惯例被阻止后无法使用。
async def example():
async with Example.scope() as sender:
await sender.send(b"Hello ")
await sender.send(b"World!")
考虑以下示例 class,其中包含需要 运行 协程进行初始化的属性:
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)
如果我在这个例子中 运行 mypy(可能启用了严格可选),它会抱怨 _connection
可以是 None 在 send
方法和代码不是类型安全的。我无法在 __init__
中初始化 _connection
变量,因为它需要在协程中异步 运行。在 __init__
之外声明变量也可能不是一个好主意。有什么办法可以解决这个问题吗?或者您推荐另一种 (OOP) 设计来解决这个问题?
目前,我要么忽略 mypy 的投诉,要么在每次使用前添加 assert self._connection
,要么在使用后添加 # type: ignore
。
It's probably a bad idea to declare the variable outside
__init__
too
这很接近。您必须在 __init__
.
class Example:
_connection: Connection
async def connect(self) -> None:
self._connection = await connect_somewhere(…)
让 类 处于不可用状态通常不是好的设计,除非对它们调用某些方法。替代方案是 dependency injection 和替代构造函数:
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(self, data: bytes) -> None:
self._connection.send(data)
这使 __init__
不再依赖于稍后调用的其他初始化程序;作为奖励,可以提供不同的连接,例如用于测试。
此处 connect
的替代构造函数仍然允许以自包含的方式创建对象(被调用者不知道如何连接)但具有完整的 async
支持。
async def example():
# create instance asynchronously
sender = await Example.connect()
await sender.send(b"Hello ")
await sender.send(b"World!")
要获得打开和关闭的完整生命周期,支持async with
是最直接的方法。这可以通过与替代构造函数类似的方式得到支持——通过提供替代构造 作为上下文管理器:
from typing import TypeVar, Type, AsyncIterable
from contextlib import asynccontextmanager
T = TypeVar('T')
class Example:
def __init__(self, connection: Connection) -> None:
self._connection = connection
@asynccontextmanager
@classmethod
async def scope(cls: Type[T]) -> AsyncIterable[T]:
connection = await connect_somewhere(...) # use `async with` if possible!
try:
yield cls(connection)
finally:
connection.close()
async def send(self, data: bytes) -> None:
self._connection.send(data)
替代connect
为简洁起见省略了构造函数。对于 Python 3.6,asynccontextmanager
可以从 the asyncstdlib
获取(免责声明:我维护这个库)。
有一个普遍的警告:关闭确实会使对象处于无法使用的状态 - 因此不一致 - 实际上根据定义。 Python 的类型系统无法将“打开 Connection
” 与“关闭 Connection
” 分开,尤其是无法检测到 .close
或上下文转换的结束从一个到另一个。
通过使用 async with
可以部分回避这个问题,因为上下文管理器通常被认为在按照惯例被阻止后无法使用。
async def example():
async with Example.scope() as sender:
await sender.send(b"Hello ")
await sender.send(b"World!")