Проверьте, нормально или асинхронно, если функция или метод

Как узнать, является ли функция или метод нормальной функцией или функцией async? Я бы хотел, чтобы мой код автоматически поддерживал обычные или асинхронные обратные вызовы и нуждался в способе проверить, какой тип функции передан.

async def exampleAsyncCb():
    pass

def exampleNomralCb():
    pass

def isAsync(someFunc):
    #do cool dynamic python stuff on the function
    return True/False

async def callCallback(cb, arg):
    if isAsync(cb):
        await cb(arg)
    else:
        cb(arg)

И в зависимости от того, какой тип функции передается, он должен либо запускать его нормально, либо с ожиданием. Я пробовал разные вещи, но понятия не имею, как реализовать isAsync().

Ответы

Ответ 1

Используйте inspect модуль Python.

inspect.iscoroutinefunction(object)

Возвращает true, если объект является сопроводительной функцией (функция, определенная с синтаксисом aync def).

Эта функция доступна с Python 3.5. Модуль доступен для Python 2 с меньшей функциональностью и, конечно же, без того, который вы ищете: inspect

Проверьте, что модуль, как следует из названия, полезен для проверки множества вещей. В документации указано

Модуль проверки предоставляет несколько полезных функций, помогающих получать информацию о живых объектах, таких как модули, классы, методы, функции, трассировки, объекты фрейма и объекты кода. Например, он может помочь вам изучить содержимое класса, получить исходный код метода, извлечь и отформатировать список аргументов для функции или получить всю необходимую информацию для отображения подробной трассировки.

Существует четыре основных вида услуг, предоставляемых этим модулем: проверка типов, получение исходного кода, проверка классов и функций и проверка стека интерпретаторов.

Некоторые основные возможности этого модуля:

inspect.ismodule(object)
inspect.isclass(object)
inspect.ismethod(object)
inspect.isfunction(object)

Он также имеет возможность извлекать исходный код

inspect.getdoc(object)
inspect.getcomments(object)
inspect.getfile(object) 
inspect.getmodule(object)

Способы называются интуитивно. Описание при необходимости можно найти в документации.

Ответ 2

Если вы не хотите вводить другой импорт с inspect, iscoroutine также доступен внутри asyncio.

import asyncio

def isAsync(someFunc):
    return asyncio.iscoroutinefunction(someFunc)

Ответ 3

Co-подпрограммы имеют флаг COROUTINE, бит 6 в флагах кода:

>>> async def foo(): pass
>>> foo.__code__.co_flags & (2 << 6)
128   # not 0, so the flag is set.

Значение 128 сохраняется как константа в модуле inspect:

>>> import inspect
>>> inspect.CO_COROUTINE
128
>>> foo.__code__.co_flags & inspect.CO_COROUTINE
128

Функция inspect.iscoroutinefunction() делает именно это; проверьте, является ли объект функцией или методом (для обеспечения атрибута __code__) и проверки для этого флага. Смотрите исходный код .

Конечно, использование inspect.iscoroutinefunction() является наиболее читаемым и гарантировано продолжит работу, если бы когда-либо изменялись флагов кода:

>>> inspect.iscoroutinefunction(foo)
True

Ответ 4

TL;DR

Если вы хотите проверить, что-то должно использоваться с await, используйте inspect.isawaitable (например, когда вы тестируете что-то callable() не просто как функцию).

В отличие от функции iscoroutine или iscoroutinefunction она также будет работать для Future и объектов, которые реализуют метод __await__.


Решения выше будут работать для простых случаев, когда вы передаете функцию сопрограммы. В некоторых случаях вам может потребоваться передать ожидаемую объектную функцию, которая действует как функция сопрограммы, но не является функцией сопрограммы. Два примера - класс Future или класс объектов, подобный Future (класс, реализующий магический метод __await__). В этом случае iscoroutinefunction вернет False, то, что вам не нужно.

Это легче понять на не асинхронном примере с передачей функции без вызова в качестве обратного вызова:

class SmartCallback:
    def __init__(self):
        print('SmartCallback is not function, but can be used as function')

await callCallback(SmartCallback)  # Should work, right?

Вернемся к асинхронному миру, похожая ситуация:

class AsyncSmartCallback:
    def __await__(self):
        return self._coro().__await__()

    async def _coro(self):
        print('AsyncSmartCallback is not coroutine function, but can be used as coroutine function')
        await asyncio.sleep(1)

await callCallback(AsyncSmartCallback)  # Should work, but oops! iscoroutinefunction(AsyncSmartCallback) == False

Чтобы решить эту проблему, не используйте iscoroutine или iscoroutinefunction, а вместо этого используйте inspect.isawaitable. Он работает с готовым объектом, поэтому сначала вы должны его создать. Другими словами, решение я бы посоветовал использовать:

async def callCallback(cb, arg):
    if callable(cb):
        res = cb()  # here result of regular func or awaitable
        if inspect.isawaitable(res):
            res = await res  # await if awaitable
        return res  # return final result
    else:
        raise ValueError('cb is not callable')

Это более универсальное (и я уверен, логически правильное) решение.