31

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

IDE:日食

插件: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.fooand分配conj.foo给两个变量,#3 和 #4 返回不同的 id。

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

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

4

2 回答 2

51

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

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

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

请参阅文档id()

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

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

强调我的。

您可以通过类的属性使用对函数的直接引用来重新创建方法__dict__,然后调用__get__描述符方法

>>> 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()from type(instance).__dict__['foo'].__get__(instance, type(instance))()with的执行路径type(instance).__dict__['foo'](instance),因此“手动”将实例直接传递给函数对象。

于 2012-11-12T17:12:11.760 回答
9

添加到@Martijn Pieters 的非常好的答案

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
于 2012-11-12T17:20:19.167 回答