Если my_wrapper
разрешено знать о AsyncClass
и SyncClass
(или вы управляете ими и можете добавить атрибут класса, например _is_sync
, который сообщает оболочке, с каким классом он имеет дело), вы можете просто осмотреть self
.
Это невозможно сделать из местоположения <my_problem>
, поскольку self
там еще не доступно; код должен возвращать одну оболочку для случаев синхронизации и асинхронности. После вызова оболочка должна обнаружить асинхронный случай и вернуть экземпляр async def
, если вам нужно асинхронное поведение. (Функция синхронизации, которая возвращает объект сопрограммы, функционально эквивалентна функции сопрограммы, так же, как обычная функция, оканчивающаяся на return some_generator()
, идеально подходит для использования в качестве генератора.)
Вот пример, который использует isinstance
для определения, какой вариант вызывается:
def my_wrapper(fn_to_wrap):
async def async_wrapper(*args, **kwargs):
await asyncio.sleep(.1)
print('Did some async stuff')
return fn_to_wrap(*args, **kwargs)
@wraps(fn_to_wrap)
def uni_wrapper(self, *args, **kwargs):
# or if self._is_async, etc.
if isinstance(self, AsyncClass):
return async_wrapper(self, *args, **kwargs)
time.sleep(.1)
print('Did some sync stuff')
return fn_to_wrap(self, *args, **kwargs)
return uni_wrapper
Эта реализация приводит к желаемому результату:
>>> x = SyncClass()
>>> x.fn_to_call()
Did some sync stuff
'Finally a return a value'
>>> async def test():
... x = AsyncClass()
... return await x.fn_to_call()
...
>>> asyncio.get_event_loop().run_until_complete(test())
Did some async stuff
'Finally a return a value'
Приведенное выше решение не будет работать, если оболочка не сможет различить SyncClass
и AsyncClass
. Есть два ограничения, которые могут помешать этому:
isinstance
не будет работать, если число подклассов не ограничено;
- пользовательский атрибут класса не будет работать, если окончательные классы не контролируются автором оболочки.
В этом случае оставшийся вариант - прибегнуть к черной магии, чтобы определить, вызывается ли функция из сопрограммы или из функции синхронизации. Черная магия удобно представлена Дэвидом Бизли в этом разговоре :
def from_coroutine():
return sys._getframe(2).f_code.co_flags & 0x380
Используя from_coroutine
, uni_wrapper
часть my_wrapper
будет выглядеть так:
@wraps(fn_to_wrap)
def uni_wrapper(*args, **kwargs):
if from_coroutine():
return async_wrapper(*args, **kwargs)
time.sleep(.1)
print('Did some sync stuff')
return fn_to_wrap(*args, **kwargs)
... с тем же результатом.
Конечно, вы должны знать, что черная магия может перестать работать в следующем выпуске Python без какого-либо предупреждения. Но если вы знаете, что делаете, это может оказаться весьма полезным.