为啥 id() == id() 和 id([]) == id([]) 在 Python 中?
Posted
技术标签:
【中文标题】为啥 id() == id() 和 id([]) == id([]) 在 Python 中?【英文标题】:Why does id() == id() and id([]) == id([]) in CPython?为什么 id() == id() 和 id([]) == id([]) 在 Python 中? 【发布时间】:2011-04-22 02:11:21 【问题描述】:为什么 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([]))
我有一些想法可能,但找不到具体的原因。
编辑
进一步证明格伦和托马斯的观点:
[1] id([])
4330909912
[2] x = []
[3] id(x)
4330909912
[4] id([])
4334243440
【问题讨论】:
哇,这很奇怪。看起来如果你得到一个新的 dict/list 的 id,然后让它的引用计数下降到零,然后得到另一个新的 dict/list,它将具有相同的 id。当其引用计数降至零时,它看起来像一个未修改的字典/列表被保存以备后用。我的猜测是,这是针对创建并立即丢弃字典/列表的代码的情况的优化。这很常见,例如:使用setdefault
的代码经常这样做。
@Potatoswatter:绝对不是。对象的ID在创建后永远不会改变,而list和dicts是可变对象,所以空lists和dicts不能像字符串和小数字那样被记忆。
@Glenn Maynard:从技术上讲,您可以通过在丢弃之前先清空它们来记忆空列表和字典;但是,与创建新处理器相比,这可能只是浪费处理器周期。
@Lie Ryan:我真的不知道你在说什么,但你不能记住空列表,因为对象的 id 必须在其生命周期内保持不变。
@Glenn Maynard:是的,你可以。从技术上讲,您可以拥有一个空列表和空字典池;每次你需要一个新的字典时,都会检查这个池,每次你处理一个列表时(即当引用计数为零时)你把列表/字典放回池中。不要求两个不同时间的两个对象不具有相同的 id()。但是,这里没有太多的节省。 我刚刚意识到,这不是记忆,而是缓存
【参考方案1】:
一旦对象超出范围,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()))
【讨论】:
那么为什么print id(); a = []; print id()
在cpython 中会打印两次相同的值呢?存储在a
中的列表不应该占用第一个字典释放的位置吗?
@Laurence:不,不一定。分配器很复杂并且经过高度优化;他们不会简单地获取可用的第一个地址。在这种情况下,dict 对象和 list 对象具有非常不同的大小,这可能会将它们放入不同的分配桶中。
在 Python 3.x 中,分配器的工作方式(在高级别)是a documented part of the the C API,而不是埋在源代码中的 cmets 中,尽管我认为特定的自定义列表和 dict/set 分配器并且 freelists 仅记录在源代码中(Objects/listobject.c
和 dictobject.c
)。
另一种查看方式是这个 sn-p:x = []; i = id(x); del x; gc.collect(); i == id([])
。如果您放弃gc.collect()
调用,这将(可能)返回False
,否则(可能)返回True
。
@filmor 这不是 CPython 垃圾收集的工作原理【参考方案2】:
当您调用id()
时,Python 会创建一个字典并将其传递给id
函数。 id
函数获取它的 id(它的内存位置),并丢弃 dict。字典被破坏了。当您快速连续执行两次(同时没有创建任何其他字典)时,Python 第二次创建的字典恰好使用与第一次相同的内存块。 (CPython 的内存分配器比听起来更有可能。)因为(在 CPython 中)id
使用内存位置作为对象 id,所以两个对象的 id 是相同的。如果您将 dict 分配给一个变量然后获取它的 id()
,这显然不会发生,因为 dicts同时是活动的,所以它们的 id
必须不同。
可变性不会直接发挥作用,但缓存元组和字符串的代码对象会发挥作用。在相同的代码对象(函数或类主体或模块主体)中,相同的文字(整数、字符串和某些元组)将被重用。可变对象永远不能重复使用,它们总是在运行时创建。
简而言之,对象的 id 仅在对象的生命周期内是唯一的。在对象被销毁之后,或者在它被创建之前,其他东西可以具有相同的 id。
【讨论】:
【参考方案3】:列表和字典上的 == 运算符不会比较对象 ID 以查看它们是否是同一个对象 - 为此使用 obj1 is obj2
。
取而代之的是 == 操作符比较 dict 列表的成员以查看它们是否相同。
【讨论】:
他不是在比较[] == []
,而是在比较id([]) == id([])
。
注意他不是在比较列表和字典,而是他们的id()
。
OP 并没有尝试这样做。
实际上,大多数 == 的实现都会先使用 is
运算符进行检查,然后再进行成员检查。原因是具有相同 id 的两个对象必须具有相同的内容。但是你是对的,id()
比较应该使用is
运算符而不是id(a) == id(b)
来完成;并且获取对象的 id() 通常是没有意义的。
@Lie Ryan 有趣的是,这在这种情况下不起作用:[] is []
是 False
。我想将第一个 []
传递给 id
会为其创建一个退出范围。以上是关于为啥 id() == id() 和 id([]) == id([]) 在 Python 中?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 id_rsa 和 id_rsa.pub 文件的内容是字母?
为啥employee_id和department_id不会在数据库Hibernate jpa中自动更新