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()
.
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.
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)
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
[~ # ~] 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).