【问题标题】:id()s of bound and unbound method objects --- sometimes the same for different objects, sometimes different for the same object绑定和未绑定方法对象的 id() --- 有时不同对象相同,有时相同对象不同
【发布时间】:2018-01-17 14:01:54
【问题描述】:

我尝试了一些关于绑定和非绑定方法的代码。当我们调用它们时,我认为它们都会返回对象。但是当我使用id() 获取一些信息时,它会返回一些我不明白的东西。

IDE:Eclipse

插件:pydev

Class C(object):
    def foo(self):
        pass

cobj = C()

print id(C.foo)    #1
print id(cobj.foo) #2

a = C.foo
b = cobj.foo

print id(a)        #3
print id(b)        #4

输出是……

5671672

5671672

5671672

5669368

为什么 #1 和 #2 返回相同的 id?它们不是不同的对象吗?如果我们将C.fooconj.foo 分配给两个变量,#3 和#4 会返回不同的 id。

我认为#3和#4表明它们不是同一个对象,而是#1和#2...

绑定方法的id和未绑定方法的id有什么区别?

【问题讨论】:

    标签: python object methods


    【解决方案1】:

    添加到@Martijn Pieters 的非常好的answer

    In [1]: class C(object):
       ...:     def foo(self):
       ...:         pass
       ...:
    
    In [2]: c = C()
    
    In [3]: id(c.foo), id(C.foo)
    Out[3]: (149751844, 149751844)  # so 149751844 is current free memory address
    
    In [4]: a = c.foo  # now 149751844 is assigned to a
    
    In [5]: id(a)              
    Out[5]: 149751844
    
    # now python will allocate some different address to c.foo and C.foo     
    
    In [6]: id(c.foo), id(C.foo)    # different address used this time, and
    Out[6]: (149752284, 149752284)  # that address is freed after this step
    
    # now 149752284 is again free, as it was not allocated to any variable
    
    In [7]: b = C.foo  # now 149752284 is allocated to b    
    
    In [8]: id(b)
    Out[8]: 149752284                
    
    In [9]: c.foo is C.foo  # better use `is` to compare objects, rather than id()
    Out[9]: False
    

    【讨论】:

      【解决方案2】:

      每当您通过instance.name(在Python 2 中为class.name)查找方法时,都会创建一个新的方法对象。 Python 每次都使用descriptor protocol 将函数包装在一个方法对象中。

      因此,当您查找 id(C.foo) 时,会创建一个新的方法对象,您检索其 id(内存地址),然后再次丢弃该方法对象。然后您查找id(cobj.foo),这是一个创建的新方法对象,它重新使用现在释放的内存地址,您会看到相同的值。然后,该方法再次被丢弃(当引用计数降至 0 时收集垃圾)。

      接下来,您将对 C.foo 未绑定方法的引用存储在变量中。现在内存地址没有被释放(引用计数为 1,而不是 0),您通过查找 cobj.foo 创建了一个 second 方法实例,该实例必须使用新的内存位置。因此,您会得到两个不同的值。

      documentation for id()

      返回对象的“身份”。这是一个整数(或长整数),保证该对象在其生命周期内是唯一且恒定的。 生命周期不重叠的两个对象可能具有相同的id()

      CPython 实现细节:这是对象在内存中的地址。

      强调我的。

      你可以通过类的__dict__属性直接引用函数来重新创建一个方法,然后调用__get__ descriptor method

      >>> class C(object):
      ...     def foo(self):
      ...         pass
      ... 
      >>> C.foo
      <unbound method C.foo>
      >>> C.__dict__['foo']
      <function foo at 0x1088cc488>
      >>> C.__dict__['foo'].__get__(None, C)
      <unbound method C.foo>
      >>> C.__dict__['foo'].__get__(C(), C)
      <bound method C.foo of <__main__.C object at 0x1088d6f90>>
      

      请注意,在 Python 3 中,整个未绑定/绑定方法的区别已被删除;你会得到一个函数,在你得到一个未绑定的方法之前,你会得到一个方法,否则,一个方法是总是绑定的:

      >>> C.foo
      <function C.foo at 0x10bc48dd0>
      >>> C.foo.__get__(None, C)
      <function C.foo at 0x10bc48dd0>
      >>> C.foo.__get__(C(), C)
      <bound method C.foo of <__main__.C object at 0x10bc65150>>
      

      此外,Python 3.7 添加了一个新的LOAD_METHOD - CALL_METHOD 操作码对,准确地替换了当前的LOAD_ATTRIBUTE - CALL_FUNCTION 操作码对,以避免每次都创建一个新的方法对象。此优化将instance.foo() 的执行路径从type(instance).__dict__['foo'].__get__(instance, type(instance))() 转换为type(instance).__dict__['foo'](instance),因此“手动”将实例直接传递给函数对象。

      【讨论】:

      • @AshwiniChaudhary:这是 CPython 的一个实现细节;其他 python 实现可能对id() 使用不同的值。
      • 可能更好看:MethodType(vars(C)['foo'], C(), C)。但这并不是说这样的代码应该司空见惯。
      • @eryksun:我的意思是首先说明如何 python 创建方法;表明它不是为一个类创建一次,然后每次都检索。使用 types.MethodType() 会破坏这个目的。
      • @Vinny:因为 Python 是高度动态的。 instance.method() 可能会导致从一次调用到下一次调用完全不同的函数对象。
      • @Vinny:由于two new bytecodes,Python 3.7 现在确实避免在最常见的情况下实际创建方法对象;这适用于在同一表达式中直接调用属性查找的情况。我已经在这里的回答中介绍了这一点。
      猜你喜欢
      • 2013-12-24
      • 1970-01-01
      • 2015-05-23
      • 2012-08-13
      • 2011-12-16
      • 1970-01-01
      相关资源
      最近更新 更多