对于一个项目,我希望能够同时具有一个库的同步和异步版本,该同步版本具有大多数逻辑部分,并且异步必须以异步方式调用该同步版本。例如,我有一个在构造函数中获取http请求者的类,该请求者在内部处理同步或异步:
.
├── async
│ └── foo.py
├── foo.py
└── main.py
└── requester.py
# requester.py
class Requester():
def connect():
return self._connect('some_address')
class AsynRequester():
async def connect():
return await self._connect('some_address')
# foo.py
class Foo:
def __init__(self, requester):
self._requester = requester
def connect(self):
self.connect_info = self._requester.connect('host') # in async version it would be called by an await internally
# async/foo.py
from foo import Foo as RawFoo
class Foo(RawFoo):
async def connect(self):
return await super(RawFoo, self).connect()
# main.py
from async.foo import Foo # from foo import Foo
from requester import AsynRequester # from requester import Requester
def main():
f = Foo(AsyncRequester()) # or Foo(Requester()) where we use sync requester
await f.connect() # or f.connect() if we are using sync methods
但是异步connect
最终调用内部调用Foo
函数的requester.connect
同步类类型的同步连接(是异步类的父级)。这是不可能的,因为requester.connect
在异步模式下使用时已在内部调用await connect
,但是它没有等待就正在调用。
我所有的测试都是针对同步版本编写的,因为异步测试效率不高,我还必须为一个版本编写测试,并确保两个版本都能正常工作。我该如何同时使用相同的逻辑同时拥有I / O调用的两个版本。
答案 0 :(得分:3)
同步版本具有大多数逻辑部分,异步必须以异步方式调用同步版本
有可能,但这需要大量工作,因为您正在有效地解决function color不匹配的问题。您的逻辑必须以异步方式编写,并且要有一些技巧才能使其在同步模式下工作。
例如,一种逻辑方法将如下所示:
# common code that doesn't assume it's either sync or async
class FooRaw:
async def connect(self):
self.connect_info = await self._to_async(self._requester.connect(ENDPOINT))
async def hello_logic(self):
await self._to_async(self.connect())
self.sock.write('hello %s %s\n' % (USERNAME, PASSWORD))
resp = await self._to_async(self.sock.readline())
assert resp.startswith('OK')
return resp
在异步环境下运行时,connect
和readline
之类的方法是协程,因此必须等待它们的返回值。另一方面,在阻塞代码self.connect
和sock.readline
中,同步函数返回具体值。但是await
是存在或缺失的语法结构,您不能在运行时将其关闭而不复制代码。
要允许相同的代码在同步和异步模式下工作,请FooRaw.hello_logic
始终等待,将其留给_to_async
方法以将结果包装为可等待运行的结果外部异步。在异步类_asincify
中等待其参数并返回结果,这基本上是一个空操作。在同步类中,它不等待就返回接收到的对象-但仍定义为async def
,因此可以等待它。在那种情况下,FooRaw.hello_logic
仍然是一个协程,但是永远不会挂起(因为它等待的“协程”是_to_async
的所有实例,它们不在asyncio之外挂起。)
有了这个设置,hello_logic
的异步实现就不需要做任何事情,只需选择正确的requester
并提供正确的_to_async
即可;从connect
继承的hello_logic
和FooRaw
会自动执行正确的操作:
class FooAsync(FooRaw):
def __init__(self):
self._requester = AsyncRequester()
@staticmethod
async def _to_async(x):
# we're running async, await X and return the result
result = await x
return result
除了实现_to_async
之外,同步版本还需要包装逻辑方法以“运行”协程:
class FooSync(FooRaw):
def __init__(self):
self._requester = SyncRequester()
@staticmethod
async def _to_async(x):
# we're running sync, X is the result we want
return x
# the following can be easily automated by a decorator
def connect(self):
return _run_sync(super().connect())
def hello_logic(self):
return _run_sync(super().hello_logic())
请注意,仅因为FooSync.hello_logic
仅是名称上的协程,才可能在事件循环外运行协程。基础请求者使用阻塞调用,因此FooRaw.connect
和其他请求者从未真正暂停,因此它们一次执行就可以完成执行。 (这类似于生成器,该生成器在不产生任何结果的情况下完成了一些工作。)此属性使_run_sync
助手很简单:
def _run_sync(coro):
try:
# start running the coroutine
coro.send(None)
except StopIteration as e:
# the coroutine has finished; return the result
# stored in the `StopIteration` exception
return e.value
else:
raise AssertionError("coroutine suspended")