Как я могу написать asyncio сопрограммы, которые, возможно, действуют как обычные функции?

Я пишу библиотеку, которую я бы хотел, чтобы конечные пользователи могли произвольно использовать, как если бы ее методы и функции не были сопрограммами.

Например, учитывая эту функцию:

@asyncio.coroutine def blah_getter(): return (yield from http_client.get('http://blahblahblah')) 

Конечный пользователь, который не хочет использовать какие-либо асинхронные функции в своем собственном коде, все равно должен импортировать asyncio и запустить это:

 >>> response = asyncio.get_event_loop().run_until_complete(blah_getter()) 

Было бы здорово, если бы я мог, внутри 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')) 

Вам нужны две функции: асинхронная сопрограмма и синхронная регулярная функция:

 @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()) 

magically_determine_if_being_yielded_from() на самом деле event_loop.is_running() но я сильно не рекомендую смешивать синхронизацию и асинхронный код в той же функции.

Я согласен с ответом Андрея, я просто хочу добавить, что если вы имеете дело с объектами, а не с функциями верхнего уровня, вы можете использовать метакласс, чтобы автоматически добавлять синхронные версии ваших асинхронных методов. См. Этот пример:

 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)) 

Применение:

 >>> 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'}>