是的,Python 会为每次访问创建新的方法对象,因为它构建了一个包装器对象以传入self
. 这称为绑定方法。
Python 使用描述符来做到这一点;函数对象有一个__get__
在类上访问时调用的方法:
>>> A.__dict__['m'].__get__(A(), A)
<bound method A.m of <__main__.A object at 0x10c29bc10>>
>>> A().m
<bound method A.m of <__main__.A object at 0x10c3af450>>
请注意,Python 不能重用A().m
; Python 是一种高度动态的语言,访问.m
行为本身可能会触发更多代码,这可能会改变A().m
下次访问时返回的行为。
@classmethod
和@staticmethod
装饰器利用这种机制来分别返回一个绑定到类的方法对象和一个普通的未绑定函数:
>>> class Foo:
... @classmethod
... def bar(cls): pass
... @staticmethod
... def baz(): pass
...
>>> Foo.__dict__['bar'].__get__(Foo(), Foo)
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo.__dict__['baz'].__get__(Foo(), Foo)
<function Foo.baz at 0x10c2a1f80>
>>> Foo().bar
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo().baz
<function Foo.baz at 0x10c2a1f80>
有关更多详细信息,请参阅Python 描述符 howto。
但是,Python 3.7 添加了一个新的LOAD_METHOD
-操作码对CALL_METHOD
来精确地替换当前的LOAD_ATTRIBUTE
-CALL_FUNCTION
操作码对,以避免每次都创建一个新的方法对象。这种优化转换了instance.foo()
from type(instance).__dict__['foo'].__get__(instance, type(instance))()
with的执行路径type(instance).__dict__['foo'](instance)
,因此“手动”将实例直接传递给函数对象。如果找到的属性不是纯 Python 函数对象,则优化会退回到正常的属性访问路径(包括绑定描述符)。