6

这个问题与(至少)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()for并为、、、、和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)

(这适用于 Python 2;对于 Python 3,为, , , , , 和, and添加类似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

但是,如果我们请求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

这是一个对象 ID 更改然后又变回的示例:

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

这是一个对象 ID 发生变化,然后不会变回的示例:

>>> 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
>>> 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

因此,整个问题由以下部分组成:

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

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

  • 导致这种变化的机制是什么?

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

  • 为什么不无限期地回收第一个身份,这样可以避免所有这些复杂性?

  • 有这些记录吗?

4

2 回答 2

5

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

属性,或更准确地说是实现描述符协议的对象。例如,Class.__dict__is not a dictbut a 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 之间存在显着差异。

于 2012-04-18T23:59:27.703 回答
2

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

请注意,CPython 对对象身份的保证很少(即保证只有一个None实例);否则,您所看到的大部分内容都是可能会改变的任意实现选择。

于 2012-04-19T03:07:06.670 回答