以下代码
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__'
这怎么发生?
答案 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__
的最重要用例上运行的工具会键入提示,实际上不会执行代码,它们会静态读取代码,并且会完全错过这些运行时更改。