hasattr说谎吗? (AttributeError:“方法”对象没有属性“ __annotations __”)

时间:2018-11-23 17:07:48

标签: python-3.x

以下代码

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

这怎么发生?

2 个答案:

答案 0 :(得分:2)

您遇到错误。因为__annotations__是字典。如果要更改值,则必须这样做:

if hasattr(foo.bar, '__annotations__'):
    foo.bar.__annotations__['return'] = 'hi'

这将使您的foo.bar的返回值为hi而不是None。我唯一不确定的是__annotations__是如何受到保护的,不允许您将它们从字典更改为字符串,但是我想这是源代码中的一些内部检查。

更新

要对签名进行更多控制,可以使用inspect模块并获取类(或方法)的Signature对象,然后从那里进行编辑。例如

import inspect
sig = inspect.signature(foo.bar)
sig.return_annotation  # prints None (before modifying)
sig.replace(return_annotation="anything you want")

有关here

的更多信息

答案 1 :(得分:1)

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

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

因此,仅因为该属性可以被 read 读取,并不意味着您可以对其进行设置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__是一个可变字典,因此直接操作该对象的键和值会更容易,这完全可以通过方法包装器来实现:< / p>

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

无论哪种方式,您completely kill important speed optimisations made in Python 3.7都是这种方式。

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