为什么同一类的不同对象的方法具有相同的id?

iam*_*ush 8 python methods object

在下面的代码中,我不明白为什么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
Run Code Online (Sandbox Code Playgroud)

Dav*_*ver 11

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

在您的条件下,他们看起来是一样的:

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)
Run Code Online (Sandbox Code Playgroud)

但是,请注意,一旦您对它们采取任何措施,它们就会变得不同:

>>> a_m = a.method
>>> b_m = b.method
>>> id(a_m), id(b_m)
(4547151*9*04, 4547151*5*04)
Run Code Online (Sandbox Code Playgroud)

然后,再次测试时,它们又改变了!

>>> id(b.method)
4547304416
>>> id(a.method)
4547304416
Run Code Online (Sandbox Code Playgroud)

访问实例上的方法时,将返回"绑定方法"的实例.绑定方法存储对实例和方法的函数对象的引用:

>>> 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
Run Code Online (Sandbox Code Playgroud)

(注意我需要使用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分配下一个绑定方法 - for - 时,它被分配到内存中的完全相同的位置,因为自分配的绑定方法以来没有任何(或已经是平衡数量的)a.method分配.这意味着它a.method似乎具有相同的内存位置b.method.

最后,这解释了为什么内存位置在第二次检查时会发生变化:第一次和第二次检查之间发生的其他分配意味着第二次将它们分配到不同的位置(注意:它们重新分配,因为对它们的所有引用都丢失了;绑定的方法被缓存†,因此访问相同的方法两次将返回相同的实例:)a_m0 = a.method; a_m1 = a.method; a_m0 is a_m1 => True.

*:pedants注意:实际上,这与实际的垃圾收集器无关,垃圾收集器只存在于处理循环引用......但......这是另一天的故事.
†:至少在CPython 2.7中; CPython 2.6似乎没有缓存绑定方法,这会让我期望未指定行为.

  • 我相信你的第二个例子,你有两个不同的引用,但a.method和b.method仍然有相同的id. (2认同)
  • `lst = [1,2,3]; len({id(lst2.append)for _ in range(1000000)})`有意为不同的运行产生不同的结果(通常在1-3之间)......应该查看源代码,看看它是如何处理的...... (2认同)

Asa*_*din 8

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

  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
Run Code Online (Sandbox Code Playgroud)

  • 啊,我们有一个胜利者!这很有道理.非常好的答案. (3认同)