Python的id()有多独特?

问题描述 投票:6回答:3

tl; dr Python重用ID吗?两个具有非重叠生命期的对象获得相同ID的可能性有多大?

背景:我一直致力于一个复杂的项目,纯粹用Python 3编写。我一直在看测试中的一些问题,并花了很多时间寻找根本原因。经过一些分析后,我怀疑当测试作为一个整体运行时(它由一个专门的调度程序编排并运行),它会重用一些模拟方法,而不是用原始方法来设置新对象。为了检查翻译是否重用,我使用了id()

问题:id()通常工作并显示对象标识符,让我告诉我的电话是在创建新实例而不是重用。但是如果两个对象相同的话,会发生什么? The documentation说:

返回对象的“标识”。这是一个整数,在该生命周期内保证该对象是唯一且恒定的。具有非重叠寿命的两个对象可具有相同的id()值。

问题:

  1. 解释器什么时候可以重用id()值?是在它随机选择相同的内存区域时吗?如果它只是随机的,似乎极不可能,但它仍然无法保证。
  2. 有没有其他方法来检查我实际引用的对象是什么?我遇到了一个我有对象的情况,它有一个模拟的方法。该对象不再使用,垃圾收集器将其销毁。在那之后我创建了一个同一个类的新对象,它得到了一个新的id(),但是这个方法得到了与它被模拟时相同的id,它实际上只是一个模拟。
  3. 有没有办法强制Python销毁给定的对象实例?从阅读中我看来没有,并且当它看不到对象的引用时它取决于垃圾收集器,但我认为无论如何都值得询问。
python python-3.x unit-testing memory
3个回答
11
投票

是的,CPython重新使用id()值。不要指望这些在Python程序中是唯一的。

这是clearly documented

返回对象的“标识”。这是一个整数,在该生命周期内保证该对象是唯一且恒定的。具有非重叠生存期的两个对象可以具有相同的id()值。

大胆强调我的。只要对象处于活动状态,id就是唯一的。从内存中删除没有引用的对象,允许将id()值重新用于另一个对象,因此不重叠的生命周期措辞。

请注意,这仅适用于CPython,python.org提供的标准实现。还有其他Python实现,例如IronPython,Jython和PyPy,它们可以自己选择如何实现id(),因为它们每个都可以在如何处理内存和对象生存期方面做出明确的选择。

解决您的具体问题:

  1. 在CPython中,id()是内存地址。新对象将插入下一个可用内存空间,因此如果特定内存地址有足够的空间来容纳下一个新对象,则将重用内存地址。在创建大小相同的新对象时,您可以在解释器中看到这个: >>> id(1234) 4546982768 >>> id(4321) 4546982768 1234文字创建一个新的整数对象,id()为其生成一个数值。由于没有对int值的进一步引用,它将再次从内存中删除。但是使用不同的整数文字再次执行相同的表达式,并且你可能会看到相同的id()值(垃圾收集运行打破循环引用可以释放更多内存,所以你也可能再也看不到相同的id()。 所以它不是随机的,但在CPython中它是内存分配算法的一个功能。
  2. 如果您需要检查特定对象,请自行引用它。如果所有你需要保证的是物体仍然“活着”,这可能是一个weakref weak reference。 例如,首先记录对象引用,然后再检查它: import weakref # record object_ref = weakref.ref(some_object) # check if it's the same object still some_other_reference is object_ref() # only true if they are the same object 弱引用不会保持对象存活,但如果它存活,那么object_ref()将返回它(否则它将返回None)。 您可以使用这种机制生成真正唯一的标识符,请参阅下文。
  3. “销毁”对象所需要做的就是删除对它的所有引用。变量(本地和全局)是参考。其他对象的属性以及容器中的条目也是如此,例如列表,元组,字典,集合等。 一旦对象的所有引用都消失了,对象上的引用计数就会降为0,然后将其删除。 仅需要垃圾收集来打破循环引用,仅引用彼此的对象,而不再对循环进行引用。因为这样的循环在没有帮助的情况下永远不会达到引用计数0,所以垃圾收集器会定期检查这些循环并中断其中一个引用以帮助从内存中清除这些对象。 因此,通过删除对它的所有引用,可以使任何对象从内存中删除(释放)。如何实现这取决于对象的引用方式。您可以要求解释器告诉您哪些对象使用gc.get_referrers() function引用给定对象,但请注意,它不会为您提供变量名称。它为您提供了对象,例如字典对象,它是将对象作为全局引用的模块的__dict__属性等。对于完全在您控制之下的代码,最多使用gc.get_referrers()作为工具来提醒自己对象的位置是什么在编写代码时引用它来删除它们。

如果必须在Python应用程序的生命周期中具有唯一标识符,则必须实现自己的工具。如果您的对象是可清除的并且支持弱引用,那么您可以使用WeakKeyDictionary instance将任意对象与UUIDs相关联:

from weakref import WeakKeyDictionary
from collections import defaultdict
from uuid import uuid4

class UniqueIdMap(WeakKeyDictionary):
    def __init__(self, dict=None):
        super().__init__(self)
        # replace data with a defaultdict to generate uuids
        self.data = defaultdict(uuid4)
        if dict is not None:
            self.update(dict)

uniqueidmap = UniqueIdMap()

def uniqueid(obj):
    """Produce a unique integer id for the object.

    Object must me *hashable*. Id is a UUID and should be unique
    across Python invocations.

    """
    return uniqueidmap[obj].int

这仍然会产生整数,但由于它们是UUID,因此不能保证它们是唯一的,但是你在生命中遇到相同ID的可能性小于被陨石撞击的可能性。见How unique is UUID?

这样即使对于具有非重叠生命期的对象,它也会为您提供唯一的ID:

>>> class Foo:
...     pass
...
>>> id(Foo())
4547149104
>>> id(Foo())  # memory address reused
4547149104
>>> uniqueid(Foo())
151797163173960170410969562162860139237
>>> uniqueid(Foo())  # but you still get a unique UUID
188632072566395632221804340107821543671

2
投票

id在当前存在的对象中是唯一的。如果垃圾收集器删除了一个对象,则未来的对象可以具有相同的id(并且很可能会)。您必须使用自己的唯一值(例如,某些uuid)以确保您引用特定对象。您也无法手动进行垃圾回收。


2
投票
  1. 只要拥有它的对象不再在任何范围内,它就可以重用id值。实际上,如果在销毁第一个对象后立即创建类似对象,则可能会重复使用它。
  2. 如果您持有一个引用(而不是weak reference),则id不会被重用,因为该对象仍然存在。如果您只是持有id值,那么您可能做错了什么。
  3. 不,但你可以删除你的参考和request the garbage collector to run。即使没有真正的实时引用,垃圾收集也可能无法收集该对象。
© www.soinside.com 2019 - 2024. All rights reserved.