为什么创建多个对象而不命名会导致它们在 Python 中具有相同的 id?
- 2024-11-28 08:37:00
- admin 原创
- 9
问题描述:
我正在用 Python (3.3.3) 做一些事情,我遇到了一些令我困惑的事情,因为据我了解,每次调用类时都会获得一个新的 id。
假设你在某个 .py 文件中有这个:
class someClass: pass
print(someClass())
print(someClass())
上面的代码返回了相同的 id,这让我很困惑,因为我正在调用它,所以它不应该是相同的,对吧?当连续两次调用同一个类时,Python 的工作方式是这样吗?当我等待几秒钟时,它会给出一个不同的 id,但如果我像上面的例子一样同时执行此操作,它似乎不会那样工作,这让我很困惑。
>>> print(someClass());print(someClass())
<__main__.someClass object at 0x0000000002D96F98>
<__main__.someClass object at 0x0000000002D96F98>
它返回相同的内容,但为什么呢?例如,我还注意到了范围
for i in range(10):
print(someClass())
当快速调用类时,Python 这样做有什么特殊原因吗?我甚至不知道 Python 会这样做,或者这可能是一个错误?如果这不是一个错误,有人可以向我解释如何修复它或一种方法,以便每次调用方法/类时都会生成不同的 id?我很困惑它是如何做到的,因为如果我等待,它确实会改变,但如果我尝试调用同一个类两次或更多次,它就不会改变。
解决方案 1:
对象的仅保证在该对象的生命周期内id
是唯一的,而不是在程序的整个生命周期内。您创建的两个对象仅在调用 的持续时间内存在- 之后,它们可供垃圾收集(并且在 CPython 中,立即释放)。由于它们的生命周期不重叠,因此它们共享一个 id 是有效的。someClass
`print`
在这种情况下,这也不足为奇,因为结合了两个 CPython 实现细节:首先,它通过引用计数进行垃圾收集(使用一些额外的魔法来避免循环引用问题),其次,id
对象的与变量的底层指针的值(即其内存位置)相关。因此,第一个对象(即最近分配的对象)会立即被释放 -下一个分配的对象最终会出现在同一位置也就不足为奇了(尽管这可能还取决于解释器的编译细节)。
如果您依赖具有不同id
s 的多个对象,您可能会将它们保留在列表中,以便它们的生命周期重叠。否则,您可能会实现具有不同保证的特定于类的 id - 例如:
class SomeClass:
next_id = 0
def __init__(self):
self.id = SomeClass.nextid
SomeClass.nextid += 1
解决方案 2:
如果你阅读的文档id
,它说:
返回对象的“标识”。这是一个整数,保证在该对象的生命周期内唯一且恒定。两个生命周期不重叠的对象可能具有相同的
id()
值。
这正是正在发生的事情:您有两个生命周期不重叠的对象,因为第一个对象在第二个对象被创建之前就已经超出了范围。
但不要相信这种情况总是会发生。特别是当你需要处理其他 Python 实现或更复杂的类时。语言所说的只是这两个对象可能具有相同的id()
值,而不是它们会具有相同的值。而它们确实会具有相同的值取决于两个实现细节:
垃圾收集器必须在代码开始分配第二个对象之前清理第一个对象 - 这在 CPython 或任何其他引用计数实现中肯定会发生(当没有循环引用时),但对于 Jython 或 IronPython 中的分代垃圾收集器来说,这种情况不太可能发生。
底层的分配器必须非常倾向于重用最近释放的相同类型的对象。这在 CPython 中是真的,它在基本 C 之上有多层花哨的分配器
malloc
,但大多数其他实现将更多的工作留给了底层虚拟机。
最后一件事: 恰巧object.__repr__
包含一个恰巧与十六进制数相同的子字符串,id
这只是 CPython 的一个实现工件,在任何地方都无法保证。根据文档:
如果可能的话,这应该看起来像一个有效的 Python 表达式,可用于重新创建具有相同值的对象(给定适当的环境)。如果不可能,
<...some useful description…>
则应返回以下形式的字符串。
CPythonobject
碰巧会 put hex(id(self))
(实际上,我相信它相当于sprintf
通过 -ing 其指针%p
,但由于 CPythonid
只是返回相同的指针,将其转换为long
最终相同的指针)这一事实在任何地方都无法保证。即使它自……以来一直如此,object
甚至在 2.x 早期就存在了。您可以放心地依靠它在交互式提示符下进行这种简单的“这里发生了什么”调试,但不要尝试超出此范围使用它。
解决方案 3:
我感觉到这里有一个更深层次的问题。你不应该依赖于id
在程序的整个生命周期内跟踪唯一实例。你应该只是将其视为每个对象实例持续时间内不保证的内存位置指示器。如果你立即创建和释放实例,那么你很可能在同一内存位置创建连续的实例。
也许您需要做的是跟踪一个类静态计数器,该计数器为每个新实例分配一个唯一的 ID,并为下一个实例增加类静态计数器。
解决方案 4:
由于没有被保留,因此它释放了第一个实例,然后由于在此期间内存没有发生任何事情,因此它会在同一位置第二次实例化。
解决方案 5:
尝试一下这个,尝试调用以下命令:
a = someClass()
for i in range(0,44):
print(someClass())
print(a)
你会看到一些不同的东西。为什么?因为“foo”循环中第一个对象释放的内存被重用了。另一方面,a
由于它被保留了,所以不会被重用。
解决方案 6:
内存位置(和 id)未被释放的一个例子是:
print([someClass() for i in range(10)])
现在所有 ID 都是唯一的。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件