首页 > 解决方案 > hasattr 说谎?(AttributeError:“方法”对象没有属性“__annotations__”)

问题描述

以下代码

class Foo:
    def bar(self) -> None:
        pass

foo = Foo()
if hasattr(foo.bar, '__annotations__'):
    foo.bar.__annotations__ = 'hi'

崩溃

AttributeError: 'method' object has no attribute '__annotations__'

这怎么可能发生?

标签: python-3.x

解决方案


此处出现属性错误是因为您无法在方法对象上设置任何属性:

>>> foo.bar.baz = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'method' object has no attribute 'baz'

这里的异常可能令人困惑,因为method对象包装了一个函数对象,并且代理属性读取访问该底层函数对象。因此,当函数上的属性存在时,hasattr()方法上将返回True

>>> hasattr(foo.bar, 'baz')
False
>>> foo.bar.__func__.baz = 42
>>> hasattr(foo.bar, 'baz')
True
>>> foo.bar.baz
42

但是,您仍然无法通过该方法设置这些属性,无论:

>>> hasattr(foo.bar, 'baz')
True
>>> foo.bar.baz = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'method' object has no attribute 'baz'

因此,仅仅因为可以读取属性并不意味着您可以设置它。hasattr()说的是实话,你只是把它解释为不同的意思。

现在,如果您尝试__annotations__直接在底层函数对象上设置属性,您会收到另一条错误消息:

>>> foo.bar.__func__.__annotations__ = 'hi'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __annotations__ must be set to a dict object

你会想在这里使用一个字典对象:

>>> foo.bar.__func__.__annotations__ = {'return': 'hi'}
>>> foo.bar.__annotations__
{'return': 'hi'}

然而,由于__annotations__是一个可变字典,直接操作该对象的键和值就更容易了,这通过方法包装器完全可行:

>>> foo.bar.__annotations__['return'] = 'int'
>>> foo.bar.__annotations__
{'return': 'int'}

现在,如果您希望为每个实例设置注释,则无法在方法对象上设置属性,因为方法对象是短暂的,它们是为调用而创建的,然后通常在之后被丢弃。

__annotations__您必须通过元类使用自定义方法描述符对象并每次为这些对象重新创建属性,或者您可以改为使用新的函数对象预先绑定方法,该函数对象将被赋予其自己的属性。然后您必须支付更大的内存价格:

import functools

foo.bar = lambda *args, **kwargs: Foo.bar(foo, *args, **kwargs)
functools.update_wrapper(foo.bar, Foo.bar)  # copy everything over to the new wrapper
foo.bar.__annotations__['return'] = 'hi'

无论哪种方式,您都会以这种方式完全扼杀 Python 3.7 中的重要速度优化

并且在最重要的用例上运行的工具__annatotions__,类型提示,实际上并不执行代码,它们静态读取代码并且会完全错过这些运行时更改。


推荐阅读