如何编写可选择作为常规函数的asyncio协同程序?

Dus*_*att 16 python python-3.x python-asyncio

我正在编写一个我希望最终用户可以选择使用的库,就像它的方法和函数不是协同程序一样.

例如,给定此功能:

@asyncio.coroutine
def blah_getter():
    return (yield from http_client.get('http://blahblahblah'))
Run Code Online (Sandbox Code Playgroud)

不关心在自己的代码中使用任何异步功能的最终用户仍然需要导入asyncio并运行:

>>> response = asyncio.get_event_loop().run_until_complete(blah_getter())
Run Code Online (Sandbox Code Playgroud)

如果可以的话,这将是很酷的,blah_getter确定我是否被称为协程,并做出相应的反应.

所以类似于:

@asyncio.coroutine
def blah_getter():
    if magically_determine_if_being_yielded_from():
        return (yield from http_client.get('http://blahblahblah'))
    else:
        el = asyncio.get_event_loop()
        return el.run_until_complete(http_client.get('http://blahblahblah'))
Run Code Online (Sandbox Code Playgroud)

And*_*lov 26

你需要两个函数 - 异步协程和同步常规函数:

@asyncio.coroutine
def async_gettter():
    return (yield from http_client.get('http://example.com'))

def sync_getter()
    return asyncio.get_event_loop().run_until_complete(async_getter())
Run Code Online (Sandbox Code Playgroud)

magically_determine_if_being_yielded_from()实际上,event_loop.is_running()但我强烈建议不要在同一个函数中混合同步和异步代码.

  • 我同意这个答案:将两者混合是一个坏主意,可能会导致混乱和意外结果."明确比隐含更好." (6认同)
  • 在考虑了一些之后,我认为你是对的.明确比隐含更好! (2认同)

dan*_*ano 16

我同意Andrew的回答,我只想补充一点,如果你处理对象而不是顶层函数,你可以使用元类自动添加异步方法的同步版本.看这个例子:

import asyncio
import aiohttp

class SyncAdder(type):
    """ A metaclass which adds synchronous version of coroutines.

    This metaclass finds all coroutine functions defined on a class
    and adds a synchronous version with a '_s' suffix appended to the
    original function name.

    """
    def __new__(cls, clsname, bases, dct, **kwargs):
        new_dct = {}
        for name,val in dct.items():
            # Make a sync version of all coroutine functions
            if asyncio.iscoroutinefunction(val):
                meth = cls.sync_maker(name)
                syncname = '{}_s'.format(name)
                meth.__name__ = syncname
                meth.__qualname__ = '{}.{}'.format(clsname, syncname)
                new_dct[syncname] = meth
        dct.update(new_dct)
        return super().__new__(cls, clsname, bases, dct)

    @staticmethod
    def sync_maker(func):
        def sync_func(self, *args, **kwargs):
            meth = getattr(self, func)
            return asyncio.get_event_loop().run_until_complete(meth(*args, **kwargs))
        return sync_func

class Stuff(metaclass=SyncAdder):
    @asyncio.coroutine
    def getter(self, url):
        return (yield from aiohttp.request('GET', url))
Run Code Online (Sandbox Code Playgroud)

用法:

>>> import aio, asyncio
>>> aio.Stuff.getter_s
<function Stuff.getter_s at 0x7f90459c2bf8>
>>> aio.Stuff.getter
<function Stuff.getter at 0x7f90459c2b70>
>>> s = aio.Stuff()
>>> s.getter_s('http://example.com')
<ClientResponse(http://example.com) [200 OK]>
<CIMultiDictProxy {'ACCEPT-RANGES': 'bytes', 'CACHE-CONTROL': 'max-age=604800', 'DATE': 'Mon, 11 May 2015 15:13:21 GMT', 'ETAG': '"359670651"', 'EXPIRES': 'Mon, 18 May 2015 15:13:21 GMT', 'SERVER': 'ECS (ewr/15BD)', 'X-CACHE': 'HIT', 'X-EC-CUSTOM-ERROR': '1', 'CONTENT-LENGTH': '1270', 'CONTENT-TYPE': 'text/html', 'LAST-MODIFIED': 'Fri, 09 Aug 2013 23:54:35 GMT', 'VIA': '1.1 xyz.com:80', 'CONNECTION': 'keep-alive'}>
>>> asyncio.get_event_loop().run_until_complete(s.getter('http://example.com'))
<ClientResponse(http://example.com) [200 OK]>
<CIMultiDictProxy {'ACCEPT-RANGES': 'bytes', 'CACHE-CONTROL': 'max-age=604800', 'DATE': 'Mon, 11 May 2015 15:25:09 GMT', 'ETAG': '"359670651"', 'EXPIRES': 'Mon, 18 May 2015 15:25:09 GMT', 'SERVER': 'ECS (ewr/15BD)', 'X-CACHE': 'HIT', 'X-EC-CUSTOM-ERROR': '1', 'CONTENT-LENGTH': '1270', 'CONTENT-TYPE': 'text/html', 'LAST-MODIFIED': 'Fri, 09 Aug 2013 23:54:35 GMT', 'VIA': '1.1 xys.com:80', 'CONNECTION': 'keep-alive'}>
Run Code Online (Sandbox Code Playgroud)

  • 对不起,不是装饰者而是包装.sync_maker()的最后一行应该是:```return functools.update_wrapper(sync_func,func)``` (2认同)