【问题标题】:Test if function or method is normal or asynchronous测试函数或方法是正常还是异步
【发布时间】:2021-12-19 22:47:50
【问题描述】:

如何确定函数或方法是普通函数还是异步函数?我希望我的代码能够自动支持正常或异步回调,并且需要一种方法来测试传递的函数类型。

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

【问题讨论】:

  • 但是...但是True/False 除以0! :O
  • 请注意,任何给定的异步函数都可以是 coroutine functionasynchronous generator function ,请参阅我的回答了解详情

标签: python python-3.x asynchronous python-3.5 coroutine


【解决方案1】:

使用 Python 的inspect 模块。

inspect.iscoroutinefunction(object)

如果对象是协程函数(使用 async def 语法定义的函数),则返回 true。

此函数从 Python 3.5 开始可用。 该模块可用于功能较少的 Python 2,当然没有您正在寻找的模块:inspect

顾名思义,检查模块对于检查很多东西很有用。文档说

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)

方法的命名很直观。如果需要,可以在文档中找到描述。

【讨论】:

  • inspect.iscoroutinefunction()asyncio.iscoroutinefunction() 之间有实际区别吗?
  • @M.I.Wright - 不,不是。基于 Python 3.6 的源代码,asyncio 版本只是从inspect 模块重新导入。
  • @AlesTeska 我没有查看源代码,但 py3.7 文档有一个说明,表明它们存在细微的不同,特别是:This method is different from inspect.iscoroutinefunction() because it returns True for generator-based coroutine functions decorated with @coroutine.
  • 好的答案 - 两个建议:1)提及inspect.isawaitable,就像其他答案之一一样 - 它检查了一个稍微不同的东西,但涵盖了某些其他情况,并注意到权衡会带来更多全面的答案,以及 2) 关于 Python 2 反向移植的旁白可能应该扩大到注意 async/await 甚至不存在于 Python 2 中,或者完全忽略。
  • @hallo 他们不一样,见docs.python.org/3/library/…different from inspect.iscoroutinefunction() because it returns True for generator-based coroutine functions decorated with @coroutine.
【解决方案2】:

如果您不想使用 inspect 引入另一个导入,iscoroutine 也可以在 asyncio 中使用。

import asyncio

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

【讨论】:

  • asyncio.iscoroutinefunction() 函数进行两个测试;它首先使用inspect.iscoroutinefunction(),如果该测试失败,则测试该函数是否是应用了@acyncio.coroutine decorator 的函数。考虑到这一点!
  • 我想知道这个解决方案支持的版本集与接受的版本相比如何。请注意,其他答案中还有其他解决方案。
【解决方案3】:

TLDR

如果你想检查一些东西应该与await一起使用,请使用inspect.isawaitable(当你测试一些东西是callable()而不是一个函数时)。

iscoroutineiscoroutinefunction 不同,它也适用于Futures 和实现__await__ 方法的对象。


详细

当您传递协程函数时,上述解决方案适用于简单的情况。在某些情况下,您可能希望传递 awaitable object 函数,该函数的作用类似于协程函数,但不是协程函数。两个例子是Future 类或Future-like object 类(implements__await__ 魔术方法的类)。在这种情况下,iscoroutinefunction 将返回 False,你不需要什么。

通过将非函数可调用作为回调传递给非异步示例更容易理解:

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

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

解决方法不要使用iscoroutineiscoroutinefunction,而是使用inspect.isawaitable。它适用于准备好的对象,因此您必须先创建它。换句话说,我建议使用的解决方案:

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

这是更通用(我确信逻辑上正确)的解决方案。

【讨论】:

  • 但是,如果一个普通函数作为回调传递,碰巧返回一个可等待的对象,那不会改变行为,在这种情况下,返回的对象也将被等待/执行。喜欢def testcb(): return AsyncSmartCallback
  • @Ecko 万一你通过了这个testcb,你会得到的结果应该是AsyncSmartCallback,什么都不会等待。发生这种情况是因为 AsyncSmartCallbacknot 可等待对象,它是返回可等待对象的类:AsyncSmartCallback() - 这是可等待对象。如果你的函数def testcb(): return AsyncSmartCallback(),这个对象将被等待。但我认为它没有任何问题:假设你通过了 def testcb(): return Callback() - 在这种情况下,Callback() 也会被执行。
  • 如果回调cb()是阻塞同步函数,这会阻塞整个事件循环,不是吗?
  • 对于python 3.9,inspect.isawaitable无法识别async def函数:``` >>> async def f(): pass ... >>> inspect.isawaitable(f) False ```
  • @makeroo 一开始我很困惑,一切都是正确的:f 是不可等待的,但 f() 是。 inspect.isawaitable(f()) True
【解决方案4】:

协程设置了 COROUTINE 标志,代码标志中的第 7 位:

>>> async def foo(): pass
>>> foo.__code__.co_flags & (1 << 7)
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() function 就是这样做的;测试对象是否是函数或方法(以确保存在__code__ 属性)并测试该标志。请参阅source code

当然,使用inspect.iscoroutinefunction() 是最易读的并且保证在代码标志发生变化时继续工作:

>>> inspect.iscoroutinefunction(foo)
True

【讨论】:

    【解决方案5】:

    扩展上面的答案。从 python 3.6 开始就有 4 types of functions 了:

    • 功能
    • 生成器函数
    • 协程函数
    • 异步生成器函数

    如果您的应用程序没有关于给定函数类型的先验知识,它可能是上述其中之一,异步函数可以是协程函数异步生成器函数。 asyncio.iscoroutinefunction(someFunc)只检查一个函数是否是协程函数,对于异步生成器,你可以使用inspect.isasyncgenfunction()。示例代码如下所示:

    import inspect, asyncio
    
    def isAsync(someFunc):
        is_async_gen = inspect.isasyncgenfunction(someFunc)
        is_coro_fn = asyncio.iscoroutinefunction(someFunc)
        return is_async_gen or is_coro_fn
    

    【讨论】:

    • 还有例如partial(some_async_func, ...) 两者都不是。似乎启发式是我们能得到的最接近的方法,因为我们试图猜测它是否返回一个可等待的而不实际调用它。我想知道async def f() -&gt; Awaitable 是否会让partial(...) 等更容易被检测到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-05
    • 2018-12-31
    • 2021-12-07
    • 2018-07-19
    • 2020-11-05
    • 1970-01-01
    相关资源
    最近更新 更多