【问题标题】:Why do methods of different objects of same class have same id?为什么同一类的不同对象的方法具有相同的ID?
【发布时间】:2013-05-19 14:43:27
【问题描述】:

在下面的代码中,我不明白为什么useless_func在属于两个不同的对象时有相同的id?

class parent(object):
   @classmethod
   def a_class_method(cls):
     print "in class method %s" % cls

   @staticmethod
   def a_static_method():
     print "static method"

   def useless_func(self):
     pass

 p1, p2 = parent(),parent()

 id(p1) == id(p2) // False

 id(p1.useless_func) == id(p2.useless_func) // True

【问题讨论】:

标签: python object methods


【解决方案1】:

这是一个非常有趣的问题!

在你的条件下,它们看起来确实是一样的:

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 =&gt; True)。

*:学究们注意:实际上,这与实际的垃圾收集器无关,它只是为了处理循环引用而存在……但是……这是另一天的故事。
†:至少在 CPython 2.7 中; CPython 2.6 似乎没有缓存绑定方法,这让我认为没有指定行为。

【讨论】:

  • 我相信你的第二个例子,你有两个不同的引用,但是 a.method 和 b.method 仍然有相同的 id。
  • @Hamish 这不是 id 的工作方式:a = [] b = a id(a) == id(b) True
  • lst = [1,2,3]; len({id(lst2.append) for _ in range(1000000)}) 实际上会为不同的运行产生不同的结果(通常在 1-3 之间)...应该查看源代码以了解它是如何处理的...
  • 其实我只是在 IPython 控制台上运行时似乎不一致...(我认为是在幕后进行一些其他调用?)
  • @DavidWolever 绑定的方法被缓存,因此两次访问相同的方法将返回相同的实例 a_m0 = a.method; a_m1 = a.method; a_m0 is a_m1 =&gt; True 。这不一定是真的。我在 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) [GCC 4.4.3]。
【解决方案2】:

这是我认为正在发生的事情:

  1. 当您取消引用p1.useless_func 时,会在内存中创建它的副本。此内存位置由id 返回
  2. 由于没有对刚刚创建的方法副本的引用,它被 GC 回收,内存地址再次可用
  3. 当您取消引用 p2.useless_func 时,会在相同的内存地址(可用)中创建它的副本,您可以再次使用 id 检索它。
  4. 第二个副本是GCd

如果您要运行一堆其他代码并再次检查实例方法的 ID,我敢打赌 ids 将彼此相同,但与原始运行不同。

此外,您可能会注意到,在 David Wolver 的示例中,一旦获得对方法副本的持久引用,ids 就会变得不同。

为了证实这个理论,这里是一个使用 Jython 的 shell 会话(与 PyPy 的结果相同),它不使用 CPython 的引用计数垃圾收集:

Jython 2.5.2 (Debian:hg/91332231a448, Jun 3 2012, 09:02:34) 
[OpenJDK Server VM (Oracle Corporation)] on java1.7.0_21
Type "help", "copyright", "credits" or "license" for more information.
>>> class parent(object):
...     def m(self):
...             pass
... 
>>> p1, p2 = parent(), parent()
>>> id(p1.m) == id(p2.m)
False

【讨论】:

  • 啊,我们赢了!这很有意义。很好的答案。
  • @jamylak 这是一个非常慷慨的编辑。事实上,我相信那里有足够的内容来保证单独的答案。
  • @Asad 我不相信,它属于这里
猜你喜欢
  • 1970-01-01
  • 2016-04-22
  • 1970-01-01
  • 2018-01-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多