为什么一些引用`x.y`的表达式会改变`id(x.y)`?

时间:2012-04-18 23:39:50

标签: python cpython

这个问题属于(至少)CPython 2.7.2和3.2.2。

假设我们定义Classobj如下。

class Class(object):

    def m(self):
        pass

    @property
    def p(self):
        return None

    @staticmethod
    def s():
        pass

obj = Class()

短版

为什么以下代码为False输出print()

print(Class.__dict__ is Class.__dict__)
print(Class.__subclasshook__ is Class.__subclasshook__)
print(Class.m is Class.m)

print(obj.__delattr__ is obj.__delattr__)
print(obj.__format__ is obj.__format__)
print(obj.__getattribute__ is obj.__getattribute__)
print(obj.__hash__ is obj.__hash__)
print(obj.__init__ is obj.__init__)
print(obj.__reduce__ is obj.__reduce__)
print(obj.__reduce_ex__ is obj.__reduce_ex__)
print(obj.__repr__ is obj.__repr__)
print(obj.__setattr__ is obj.__setattr__)
print(obj.__sizeof__ is obj.__sizeof__)
print(obj.__str__ is obj.__str__)
print(obj.__subclasshook__ is obj.__subclasshook__)
print(obj.m is obj.m)

(适用于Python 2;对于Python 3,省略print()的{​​{1}},并为Class.mprint(),{{添加类似的obj.__eq__ 1}},obj.__ge__obj.__gt__obj.__le__

另一方面,为什么以下代码会为每个obj.__lt__输出obj.__ne__

True

(适用于Python 2;对于Python 3,为print()添加类似的print(Class.__class__ is Class.__class__) print(Class.__delattr__ is Class.__delattr__) print(Class.__doc__ is Class.__doc__) print(Class.__format__ is Class.__format__) print(Class.__getattribute__ is Class.__getattribute__) print(Class.__hash__ is Class.__hash__) print(Class.__init__ is Class.__init__) print(Class.__module__ is Class.__module__) print(Class.__new__ is Class.__new__) print(Class.__reduce__ is Class.__reduce__) print(Class.__reduce_ex__ is Class.__reduce_ex__) print(Class.__repr__ is Class.__repr__) print(Class.__setattr__ is Class.__setattr__) print(Class.__sizeof__ is Class.__sizeof__) print(Class.__str__ is Class.__str__) print(Class.__weakref__ is Class.__weakref__) print(Class.p is Class.p) print(Class.s is Class.s) print(obj.__class__ is obj.__class__) print(obj.__dict__ is obj.__dict__) print(obj.__doc__ is obj.__doc__) print(obj.__module__ is obj.__module__) print(obj.__new__ is obj.__new__) print(obj.__weakref__ is obj.__weakref__) print(obj.p is obj.p) print(obj.s is obj.s) print()Class.__eq__Class.__ge__,{{1} },Class.__gt__Class.__le__

长版

如果我们连续两次要求Class.__lt__,我们(不出所料)会两次获得相同的对象ID。

Class.__ne__

但是,如果我们要求Class.m,然后评估一些引用id(obj.m)的表达式,然后再次询问>>> id(obj.m) 139675714789856 >>> id(obj.m) 139675714789856 ,我们有时(但不总是)发现对象ID已经改变。在其中一些更改的情况中,再次请求id(obj.m)会导致ID更改回原始值。在那些不改变的情况下,重复obj.m次调用之间的表达式显然会导致ID在两个观察值之间交替。

以下是对象ID不会更改的一些示例:

id(obj.m)

以下是对象ID更改的示例,然后更改回来:

id(obj.m)

以下是对象ID更改的示例,然后不会更改回来:

id(obj.m)

以下是相同的示例,操作表达式重复几次以演示交替行为:

>>> print(obj.m); id(obj.m)
<bound method Class.m of <__main__.Class object at 0x7f08c96058d0>>
139675714789856
>>> obj.m is None; id(obj.m)
False
139675714789856
>>> obj.m.__func__.__name__; id(obj.m)
'm'
139675714789856
>>> obj.m(); id(obj.m)
139675714789856

因此,整个问题包括以下部分:

  • 哪些属性可能会改变其身份,作为不修改这些属性的表达式的副作用?

  • 哪种表达方式会触发此类更改?

  • 导致此类更改的机制是什么?

  • 过去的身份在什么条件下被回收?

  • 为什么不能无限期地回收第一个身份,这会避免所有这些并发症?

  • 是否有任何记录?

2 个答案:

答案 0 :(得分:5)

  

哪种属性可能会改变其身份,作为不修改这些属性的表达式的副作用?

属性,或更准确地说是实现descriptor protocol的对象。例如,Class.__dict__不是dict,而是dictproxy。显然,每次请求时都会重新生成此对象。为什么?可能会减少创建对象的开销,直到有必要这样做。但是,这是一个实现细节。重要的是__dict__按照记录的方式工作。

即使是普通的实例方法也是使用描述符来处理的,这解释了为什么obj.m is not obj.m。有趣的是,如果您执行obj.m = obj.m永久将该方法包装器存储在实例上,然后obj.m is obj.m。 : - )

  

哪种表达式触发了这些变化?

对属性的任何访问都可以触发描述符的__get__()方法,并且此方法始终可以返回相同的对象或每次返回不同的对象。

  

导致此类变化的机制是什么?

属性/描述符。

  

过去的身份在什么条件下被回收?

不确定“再生”是什么意思。你的意思是“处置”或“重复使用”?在CPython中,对象的id是其内存位置。如果两个对象在不同时间到达同一个内存位置,它们将具有相同的id。因此,两个在不同时间具有相同id 的引用(即使在单个语句中)也不一定是同一个对象。其他Python实现使用不同的规则来生成id。例如,我相信Jython使用递增整数,这可以更清晰地说明对象标识。

  

为什么不能无限期地回收第一个身份,这会避免所有这些并发症?

据推测,使用描述符有一些优势。 Python解释器的源代码是可用的;如果你想了解更多细节,请看看。

  

是否有任何记载?

没有。这些是CPython解释器的特定于实现的细节,不应该依赖它们。其他Python实现(包括CPython的未来版本)可能并且很可能会以不同的方式运行。例如,2.x和3.x CPython之间存在显着差异。

答案 1 :(得分:2)

编写x.y时,会创建绑定或未绑定的方法。这是一个新对象。它可以在内存中的任何地方。如果你写x.y并且不使用结果,那么它的refcnt可以归零。这意味着内存可用,可以在下一个x.y使用,可能在同一个位置,但不一定。

请注意,CPython对对象标识的保证很少(即,您可以保证只有一个 None 的实例);否则,您所看到的大部分内容都是可能发生变化的任意实施选择。