Python方法访问器在每次访问时创建新对象?

Python方法访问器在每次访问时创建新对象?,python,methods,python-internals,Python,Methods,Python Internals,在调查时,我发现如下情况: >>> class A: ... def m(self): return 42 ... >>> a = A() 这是预期的: >>> A.m == A.m True >>> a.m == a.m True 但我没有想到: 尤其不是这个: >>> A.m is A.m False Python似乎为每个方法访问创建了新对象。为什么我会看到这种行为?也就是说,它不能为每个

在调查时,我发现如下情况:

>>> class A:
...   def m(self): return 42
... 
>>> a = A()
这是预期的:

>>> A.m == A.m
True
>>> a.m == a.m
True
但我没有想到:

尤其不是这个:

>>> A.m is A.m
False

Python似乎为每个方法访问创建了新对象。为什么我会看到这种行为?也就是说,它不能为每个类和每个实例重用一个对象的原因是什么?

是的,Python为每个访问创建新的方法对象,因为它构建了一个包装器对象以传入
self
。这称为绑定方法

Python使用描述符来实现这一点;函数对象有一个
\uuuuu get\uuuu
方法,在类上访问时调用该方法:

>>> 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>>
有关更多详细信息,请参阅


但是,Python3.7添加了一个新的
LOAD\u方法
-
CALL\u方法
操作码对,它精确地替换了当前的
LOAD\u属性
-
CALL\u函数
操作码对,以避免每次创建新的方法对象。此优化将
instance.foo()
的执行路径从
type(instance).\uu dict\uuu['foo'].\uu get\uuu(instance,type(instance))(
转换为
type(instance).\uu dict\uuuu['foo'](instance)
,因此将实例直接“手动”传递给函数对象。如果找到的属性不是纯python函数对象,则优化会返回到正常的属性访问路径(包括绑定描述符)。

因为这是实现绑定方法的最方便、最不神奇和最节省空间的方法

如果您不知道,绑定方法指的是能够执行以下操作:

f = obj.m
# ... in another place, at another time
f(args, but, not, self)
函数是描述符。描述符是一般对象,当作为类或对象的属性访问时,它们的行为可能会有所不同。它们用于实现
属性
类方法
静态方法
,以及其他一些功能。函数描述符的具体操作是,它们为类访问返回自身,为实例访问返回新绑定的方法对象。(实际上,这仅适用于Python3;Python2在这方面更复杂,它有“未绑定方法”,基本上是函数,但不完全是函数)


每次访问时都会创建一个新对象的原因是简单和高效:为每个实例的每个方法预先创建绑定方法需要时间和空间。按需创建它们而从不释放它们是一种潜在的内存泄漏(尽管CPython对其他内置类型也做了类似的操作),并且在某些情况下会稍微慢一点。复杂的基于weakref的缓存方案方法对象也不是免费的,而且要复杂得多(从历史上看,绑定方法比weakrefs早得多)。

我明白了,这实际上就是我正在研究的。但是,我希望它至少在每次静态调用中重用对象。@Krumelur:为什么会这样?Python是一种动态语言,仅仅访问
a.m
的行为就可能触发下一次访问时替换
m
的代码。但这与我的直觉背道而驰。这绝对是有道理的,我希望能有一个这样的答案。我原以为方法调用需要非常快,而对象创建太慢。但是迭代方法调用性能并不是Python的优势之一,我特别喜欢“最不神奇的”。我希望其他人更经常地考虑这个解决方案:“@ Krumelur您说得对,方法调用比大多数语言慢,但只是部分地受绑定方法对象的影响。属性查找和普通函数调用都已经(相对)昂贵,至少CPython和PyPy的“方法缓存”降低了成本。
>>> 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>
f = obj.m
# ... in another place, at another time
f(args, but, not, self)