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 了半天也没搞明白为什么,求指教!
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中反射含有协程方法的类时遇到问题如何解决?
个人猜测,应该原因在这里:
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())
主要解决思路:
- 用
getattr()正常获取方法 - 用
inspect.iscoroutinefunction()或asyncio.iscoroutinefunction()判断是否为协程函数 - 如果是协程函数就用
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 ,确实是装饰器引起的,看来有必要对装饰器与描述器的原理深入研究一波…
装饰器没什么好研究的 不要想太多
就一套娃语法糖
正常的闭包应用
特殊就描述器

