web-dev-qa-db-fra.com

Tester si la fonction ou la méthode est normale ou asynchrone

Comment savoir si une fonction ou une méthode est une fonction normale ou une fonction asynchrone? Je voudrais que mon code prenne automatiquement en charge les rappels normaux ou asynchrones et ai besoin d'un moyen de tester quel type de fonction est passé.

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)

Et selon le type de fonction qui est transmis, il devrait soit l'exécuter normalement, soit attendre. J'ai essayé différentes choses, mais je n'ai aucune idée de comment implémenter isAsync().

34
Ecko

Utilisez le module inspect de Python.

inspect.iscoroutinefunction(object)

Renvoie vrai si l'objet est une fonction coroutine (une fonction définie avec une syntaxe async def).

Cette fonction est disponible depuis Python 3.5. Le module est disponible pour Python 2 avec des fonctionnalités moindres et certainement sans celui que vous recherchez: inspecter

Inspectez le module comme son nom l'indique est utile pour inspecter beaucoup de choses. La documentation dit

Le module d'inspection fournit plusieurs fonctions utiles pour vous aider à obtenir des informations sur les objets actifs tels que les modules, les classes, les méthodes, les fonctions, les traces, les objets de trame et les objets de code. Par exemple, il peut vous aider à examiner le contenu d'une classe, à récupérer le code source d'une méthode, à extraire et à formater la liste d'arguments d'une fonction, ou à obtenir toutes les informations dont vous avez besoin pour afficher un suivi détaillé.

Il existe quatre principaux types de services fournis par ce module: vérification de type, obtention du code source, inspection des classes et des fonctions et examen de la pile d'interpréteur.

Certaines capacités de base de ce module sont:

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

Il contient également la capacité de récupérer le code source

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

Les méthodes sont nommées intuitivement. Si nécessaire, une description peut être trouvée dans la documentation.

39
Sharad

Si vous ne souhaitez pas introduire une autre importation avec inspect, iscoroutine est également disponible dans asyncio.

import asyncio

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

Les co-routines ont le drapeau COROUTINE défini, bit 7 dans les drapeaux de code:

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

La valeur 128 est stockée sous forme de constante dans le module inspect:

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

La fonction inspect.iscoroutinefunction() fait exactement cela; tester si l'objet est une fonction ou une méthode (pour s'assurer qu'il y a un attribut __code__) et tester pour cet indicateur. Voir code source .

Bien sûr, l'utilisation de inspect.iscoroutinefunction() est la plus lisible et garantie de continuer à fonctionner si jamais les indicateurs de code devaient changer:

>>> inspect.iscoroutinefunction(foo)
True
20
Martijn Pieters

[~ # ~] tldr [~ # ~]

Si vous voulez vérifier que quelque chose doit être utilisé avec await, utilisez inspect.isawaitable (Comme lorsque vous testez quelque chose étant callable() autre que simplement être une fonction).

Contrairement à iscoroutine ou iscoroutinefunction, cela fonctionnera également pour les Future et les objets qui implémentent la méthode __await__.


Détaillé

Les solutions ci-dessus fonctionneront pour les cas simples, lorsque vous passez la fonction coroutine. Dans certains cas, vous pouvez passer la fonction objet attendu qui agit comme une fonction coroutine, mais qui n'est pas une fonction coroutine. Deux exemples sont Future class or Future-like object class (class that implements__await__ Magic method). Dans ce cas, iscoroutinefunction renverra False, ce dont vous n'avez pas besoin.

Il est plus facile à comprendre sur un exemple non asynchrone en passant la fonction non appelable comme rappel:

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

callCallback(SmartCallback)  # Should work, right?

Retour au monde asynchrone, une situation similaire:

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

Manière de le résoudre de ne pas utiliser iscoroutine ou iscoroutinefunction, mais utilisez inspect.isawaitable À la place. Il fonctionne avec un objet prêt, vous devez donc d'abord le créer. En d'autres termes, la solution que je conseillerais d'utiliser:

async def callCallback(cb, arg):
    if callable(cb):
        res = cb()  # here's 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')

C'est une solution plus universelle (et je suis sûr logiquement correcte).

17
Mikhail Gerasimov