V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
conn4575
V2EX  ›  Python

Python 反射含有协程方法类的问题

  •  
  •   conn4575 · 2018-06-18 12:05:54 +08:00 · 2656 次点击
    这是一个创建于 2110 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目有个需求要动态获取模块内的所有协程方法,查了点资料发现 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'
    
    7 条回复    2018-06-19 10:26:51 +08:00
    wwqgtxx
        1
    wwqgtxx  
       2018-06-18 13:20:41 +08:00
    个人猜测,应该原因在这里:

    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 了,至于为什么这么判断,那就得看写这段代码的人是怎么想的了
    ruoyu0088
        2
    ruoyu0088  
       2018-06-18 13:27:55 +08:00
    你需要了解一下 classmethod 的原理,相关关键词为:descriptor decorator
    wwqgtxx
        3
    wwqgtxx  
       2018-06-18 13:42:51 +08:00 via iPhone
    @ruoyu0088 感觉作为装饰器,staticmethod 和 vlassmethod 是 builtin 实现的,很多特性也就不能以普通 decorator 来看待
    lolizeppelin
        4
    lolizeppelin  
       2018-06-19 09:37:53 +08:00 via Android
    楼上的关键词是对的 你就不要乱假设

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

    描述器是 Python 的特殊玩意
    lolizeppelin
        5
    lolizeppelin  
       2018-06-19 09:41:39 +08:00 via Android
    python 里面反射对象可以使用 getattr()和对象内置的__dict__属性两种方式

    这就是你错误的地方 你想简单了
    吃透描述器你就知道原理了
    conn4575
        6
    conn4575  
    OP
       2018-06-19 09:47:56 +08:00
    感谢楼上几位,根据楼上给的关键字查了一下官方文档 https://docs.python.org/3.6/howto/descriptor.html ,确实是装饰器引起的,看来有必要对装饰器与描述器的原理深入研究一波...
    lolizeppelin
        7
    lolizeppelin  
       2018-06-19 10:26:51 +08:00 via Android   ❤️ 1
    装饰器没什么好研究的 不要想太多
    就一套娃语法糖

    正常的闭包应用

    特殊就描述器
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3034 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 14:38 · PVG 22:38 · LAX 07:38 · JFK 10:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.