为何在CPython中id({}) == id({})且id([]) == id([])?

30 投票
2 回答
2738 浏览
提问于 2025-04-16 05:06

为什么CPython(对其他Python实现不太了解)会有这样的表现呢?

tuple1 = ()
tuple2 = ()                                                                                                   
dict1 = {}
dict2 = {}
list1 = []
list2 = []
# makes sense, tuples are immutable
assert(id(tuple1) == id(tuple2))
# also makes sense dicts are mutable
assert(id(dict1) != id(dict2))
# lists are mutable too
assert(id(list1) != id(list2))
assert(id(()) == id(()))
# why no assertion error on this?
assert(id({}) == id({}))
# or this?
assert(id([]) == id([]))

我有一些想法,可能是这样,但找不到一个明确的理由。

编辑

为了进一步证明Glenn和Thomas的观点:

[1] id([])
4330909912
[2] x = []
[3] id(x)
4330909912
[4] id([])
4334243440

2 个回答

45

当你调用 id({}) 时,Python 会创建一个字典(dict),然后把它传给 id 函数。这个 id 函数会获取这个字典的唯一标识(其实就是它在内存中的位置),然后就把这个字典丢掉了。这样一来,这个字典就被销毁了。如果你在很短的时间内连续调用两次这个函数(中间没有创建其他字典),Python 第二次创建的字典可能会使用和第一次相同的内存位置。(CPython 的内存分配器让这种情况发生的可能性比你想象的要高。)因为在 CPython 中,id 是用内存位置作为对象的标识,所以这两个对象的 id 就是一样的。如果你把字典赋值给一个变量,然后再获取它的 id(),就不会出现这种情况,因为这两个字典是同时存在的,所以它们的 id 必然不同。

可变性在这里并不是直接相关的,但代码对象缓存的元组和字符串是有关系的。在同一个代码对象(比如函数、类体或模块体)中,相同的字面量(整数、字符串和某些元组)会被重复使用。可变对象是不能被重复使用的,它们总是在运行时创建。

简单来说,一个对象的 id 只有在对象存在的期间是唯一的。对象被销毁后,或者在它被创建之前,其他东西可能会使用相同的 id。

42

CPython会在对象超出作用域后立即进行垃圾回收,所以第二个[]是在第一个[]被回收之后创建的。因此,大多数情况下,它们会在同一个内存位置上。

这段代码很清楚地展示了发生了什么(在其他Python实现中,输出可能会有所不同):

class A:
    def __init__(self): print("a")
    def __del__(self): print("b")

# a a b b False
print(A() is A())
# a b a b True
print(id(A()) == id(A()))

撰写回答