这是一个非常有趣的问题!
在你的条件下,它们看起来是一样的:
Python 2.7.2 (default, Oct 11 2012, 20:14:37)
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo(object):
... def method(self): pass
...
>>> a, b = Foo(), Foo()
>>> a.method == b.method
False
>>> id(a.method), id(b.method)
(4547151904, 4547151904)
但需要注意的是,一旦对它们进行任何操作,它们就会变得不同:
>>> a_m = a.method
>>> b_m = b.method
>>> id(a_m), id(b_m)
(4547151*9*04, 4547151*5*04)
然后,当再次测试时,它们又发生了变化!
>>> id(b.method)
4547304416
>>> id(a.method)
4547304416
当访问实例上的方法时,将返回“绑定方法”的实例。绑定方法存储对实例和方法函数对象的引用:
>>> a_m
<bound method Foo.method of <__main__.Foo object at 0x10f0e9a90>>
>>> a_m.im_func is Foo.__dict__['method']
True
>>> a_m.im_self is a
True
请注意,我需要使用
Foo.__dict__['method']
,而不是
Foo.method
,因为
Foo.method
会产生一个"未绑定方法" … 这个目的留给读者自己思考。
这个“绑定方法”对象的目的是使方法像函数一样在传递时表现得“合理”。例如,当我调用函数
a_m()
时,那就等同于调用
a.method()
,尽管我们不再有对
a
的显式引用。与之相比,在 JavaScript(例如)中,
var method = foo.method; method()
并不能产生与
foo.method()
相同的结果。
因此,我们回到最初的问题:为什么看起来
id(a.method)
和
id(b.method)
返回相同的值呢?我认为 Asad 是正确的:这与 Python 的引用计数垃圾收集器有关*:当评估表达式
id(a.method)
时,会分配一个绑定方法,计算 ID,然后释放绑定方法。当分配下一个绑定方法——
b.method
的绑定方法——时,它被分配到了完全相同的内存位置,因为自从分配
a.method
的绑定方法以来没有发生任何(或有平衡数量的)分配。这意味着
a.method
看起来具有与
b.method
相同的内存位置。
最后,这解释了为什么第二次检查时内存位置似乎发生了变化:在第一次和第二次检查之间进行的其他分配意味着它们第二次被分配到不同的位置(注意:它们被重新分配是因为所有对它们的引用都丢失了;绑定方法被缓存†,因此访问相同的方法两次将返回相同的实例:
a_m0 = a.method; a_m1 = a.method; a_m0 is a_m1 => True
)。
*: 吹毛求疵的人注意:实际上,这与实际的垃圾收集器无关,后者只存在于处理循环引用时……但是……那是另一个故事。†:至少在 CPython 2.7 中是这样;CPython 2.6 似乎没有缓存绑定方法,这使我期望该行为未被指定。