Python中反射含有协程方法的类时遇到问题如何解决?

项目有个需求要动态获取模块内的所有协程方法,查了点资料发现 python 里面反射对象可以使用getattr()和对象内置的__dict__属性两种方式,但是我发现两种方式再处理静态的协程方法时结果不一样:

  • 使用getattr()
a = __import__('a')
print('===========getattr()=============')
func_sync = getattr(a, 'func_sync')
func_async = getattr(a, 'func_async')
print(func_sync)  # <function func_sync at 0x7f827b98f510>
print(func_async)  # <function func_async at 0x7f8279cd01e0>
print(asyncio.iscoroutinefunction(func_async))  # True
# getattr class
A = getattr(a, 'A')
print(A)  # <class 'a.A'>
method_aa = getattr(A, 'aa')
method_bb = getattr(A, 'bb')
method_cc = getattr(A, 'cc')
print(method_aa)  # <bound method A.aa of <class 'a.A'>>  <----这里的 bound method 是什么意思?
print(method_bb)  # <function A.bb at 0x7f8279cd00d0>
print(method_cc)  # <function A.cc at 0x7f8279cd0158>
print(asyncio.iscoroutinefunction(method_aa))  # True  <---- 注意这里
print(asyncio.iscoroutinefunction(method_bb))  # True
print(asyncio.iscoroutinefunction(method_cc))  # False
  • 使用__dict__
print('=========== __dict__ =============')
A = a.__dict__['A']
func_sync = a.__dict__['func_sync']
func_async = a.__dict__['func_async']
print(asyncio.iscoroutinefunction(func_async))  # True
print(A)  # <class 'a.A'>
method_aa = A.__dict__['aa']
method_bb = A.__dict__['bb']
method_cc = A.__dict__['cc']
print(method_aa)  # <classmethod object at 0x7f827a21c908>  <---- 变成了 classmethod
print(method_bb)  # <function A.bb at 0x7f8279cd00d0>
print(method_cc)  # <function A.cc at 0x7f8279cd0158>
print(asyncio.iscoroutinefunction(method_aa))  # False <----- 不应该是 True 吗?
print(asyncio.iscoroutinefunction(method_bb))  # True
print(asyncio.iscoroutinefunction(method_cc))  # False

我感觉__dict__getattr()在反射对象的机制上应该有一些区别,但是 google 了半天也没搞明白为什么,求指教!

a.py

class A:
@classmethod
async def aa(cls):
    return 123

async def bb(self):
    return 456

def cc(self):
    return 789

def func_sync(): return ‘sync’

async def func_async(): return ‘async’


Python中反射含有协程方法的类时遇到问题如何解决?

8 回复

个人猜测,应该原因在这里:

asyncio.iscoroutinefunction:
return (getattr(func, ‘_is_coroutine’, None) is _is_coroutine or _inspect_iscoroutinefunction(func))

inspect.iscoroutinefunction:
return bool((isfunction(object) or ismethod(object)) andobject.code.co_flags & CO_COROUTINE)

通过实验
>>>A.aa
<bound method A.aa of <class ‘A’>>
>>>A.dict[“aa”]
<classmethod object at 0x000002BDE92F7F60>
>>>inspect.ismethod(A.dict[“aa”])
False
>>>inspect.isfunction(A.dict[“aa”])
False
>>>inspect.ismethod(A.aa)
True
>>>inspect.isfunction(A.aa)
False

所以你通过 A.dict[‘aa’]得到的是一个 classmethod object 而根本就不是一个 method 也不是一个 function,当然他也就不是一个 coroutinefunction 了,至于为什么这么判断,那就得看写这段代码的人是怎么想的了


遇到反射含有协程方法的类时,核心问题是getattr()获取到的是协程函数对象,而不是普通方法。直接调用会返回一个协程对象,而不是执行它。这里的关键是要区分获取和调用两个步骤。

下面是一个完整的解决方案示例:

import asyncio
import inspect

class MyClass:
    async def async_method(self):
        print("异步方法执行中...")
        await asyncio.sleep(1)
        return "异步方法结果"
    
    def sync_method(self):
        return "同步方法结果"

async def main():
    obj = MyClass()
    
    # 获取方法名
    method_name = "async_method"
    
    # 使用getattr获取方法
    method = getattr(obj, method_name)
    
    # 检查是否为协程函数
    if inspect.iscoroutinefunction(method):
        # 如果是协程函数,需要await调用
        result = await method()
        print(f"异步方法结果: {result}")
    else:
        # 普通方法直接调用
        result = method()
        print(f"同步方法结果: {result}")
    
    # 也可以这样统一处理
    method_name2 = "sync_method"
    method2 = getattr(obj, method_name2)
    
    if asyncio.iscoroutinefunction(method2):
        result2 = await method2()
    else:
        result2 = method2()
    print(f"方法2结果: {result2}")

# 运行示例
if __name__ == "__main__":
    asyncio.run(main())

主要解决思路:

  1. getattr()正常获取方法
  2. inspect.iscoroutinefunction()asyncio.iscoroutinefunction()判断是否为协程函数
  3. 如果是协程函数就用await调用,否则直接调用

如果要在同步代码中调用异步方法,需要创建事件循环:

import asyncio

method = getattr(obj, "async_method")
if asyncio.iscoroutinefunction(method):
    loop = asyncio.new_event_loop()
    result = loop.run_until_complete(method())
    loop.close()

总之反射协程方法时多一步判断就行了。

你需要了解一下 classmethod 的原理,相关关键词为:descriptor decorator

感觉作为装饰器,staticmethod 和 vlassmethod 是 builtin 实现的,很多特性也就不能以普通 decorator 来看待

楼上的关键词是对的 你就不要乱假设

这种类装饰器的实现原理叫描述器 descriptor

描述器是 Python 的特殊玩意

python 里面反射对象可以使用 getattr()和对象内置的__dict__属性两种方式

这就是你错误的地方 你想简单了
吃透描述器你就知道原理了

感谢楼上几位,根据楼上给的关键字查了一下官方文档 https://docs.python.org/3.6/howto/descriptor.html ,确实是装饰器引起的,看来有必要对装饰器与描述器的原理深入研究一波…

装饰器没什么好研究的 不要想太多
就一套娃语法糖

正常的闭包应用

特殊就描述器

回到顶部