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

nis*_*vid 6 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()
Run Code Online (Sandbox Code Playgroud)

精简版

为什么下面的代码输出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)
Run Code Online (Sandbox Code Playgroud)

(这是用于Python 2;对于Python 3中,省略了print()Class.m与添加类似于print()对于s obj.__eq__,obj.__ge__,obj.__gt__,obj.__le__,obj.__lt__,和obj.__ne__)

另一方面,为什么会True为每个代码输出以下代码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)
Run Code Online (Sandbox Code Playgroud)

(这是用于Python 2;对于Python 3中,添加类似于print()对于s Class.__eq__,Class.__ge__,Class.__gt__,Class.__le__,Class.__lt__,和Class.__ne__,和Class.m)

长版

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

>>> id(obj.m)
139675714789856
>>> id(obj.m)
139675714789856
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

>>> obj.m; id(obj.m); id(obj.m)
<bound method Class.m of <__main__.Class object at 0x7f08c96058d0>>
139675715407536
139675714789856
Run Code Online (Sandbox Code Playgroud)

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

>>> obj.m is obj.m; id(obj.m); id(obj.m)
False
139675715407536
139675715407536
Run Code Online (Sandbox Code Playgroud)

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

>>> obj.m is obj.m; id(obj.m); id(obj.m)
False
139675714789856
139675714789856
>>> obj.m is obj.m; id(obj.m); id(obj.m)
False
139675715407536
139675715407536
>>> obj.m is obj.m; id(obj.m); id(obj.m)
False
139675714789856
139675714789856
Run Code Online (Sandbox Code Playgroud)

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

  • 什么类型的属性可能会改变它们的身份作为不修改这些属性的表达式的副作用?

  • 什么样的表达式触发这种变化?

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

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

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

  • 是否有任何记录?

kin*_*all 5

什么类型的属性可能会改变他们的身份作为不修改这些属性的表达式的副作用?

属性,或者更准确地说是实现描述符协议的对象.例如,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实现使用不同的规则来生成ids.例如,我相信Jython使用递增整数,这可以更清晰地说明对象身份.

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

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

这是有记录的吗?

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