Да, CPython повторно использует id()
значения. Не рассчитывайте, что они уникальны в программе Python .
Это четко задокументировано :
Возвращает «идентичность» объекта. Это целое число, которое гарантированно будет уникальным и постоянным для этого объекта в течение срока его службы. Два объекта с неперекрывающимися временами жизни могут иметь одинаковое значение id ().
Смелый акцент мой. Идентификатор уникален только при условии, что объект жив . Объекты, которые не имеют ссылок на них, удаляются из памяти, что позволяет повторно использовать значение id()
для другого объекта, следовательно, непересекающиеся времена жизни формулировка.
Обратите внимание, что это относится только к CPython, стандартной реализации, предоставляемой python.org. Существуют и другие реализации Python, такие как IronPython, Jython и PyPy, которые сами выбирают, как реализовать id()
, потому что каждая из них может сделать свой выбор в отношении обработки времени жизни памяти и объектов.
Чтобы ответить на ваши конкретные вопросы:
В CPython id()
- это адрес памяти. Новые объекты будут размещены в следующем доступном пространстве памяти, поэтому, если на конкретном адресе памяти достаточно места для размещения следующего нового объекта, адрес памяти будет использован повторно. Это можно увидеть в интерпретаторе при создании новых объектов одинакового размера:
>>> id(1234)
4546982768
>>> id(4321)
4546982768
Литерал 1234
создает новый целочисленный объект, для которого id()
создает числовое значение. Поскольку больше нет ссылок на значение int
, оно снова удаляется из памяти. Но снова выполняя то же выражение с другим целочисленным литералом, и, скорее всего, вы увидите то же значение id()
(запуск сборщика мусора, прерывающий циклические ссылки, может освободить больше памяти, поэтому вы можете также не см. Тот же id()
снова.
Так что не случайно , но в CPython это функция алгоритмов выделения памяти.
Если вам нужно проверить определенные объекты, сохраните собственную ссылку на него . Это может быть weakref
слабая ссылка , если все, что вам нужно, это убедиться, что объект все еще "жив".
Например, сначала запись ссылки на объект, а затем проверка:
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
).
Вы можете использовать такой механизм для генерации действительно уникальных идентификаторов, см. Ниже.
Все, что вам нужно сделать, чтобы «уничтожить» объект, это удалить все ссылки на него . Переменные (локальные и глобальные) являются ссылками. Как и атрибуты других объектов, а также записи в контейнерах, таких как списки, кортежи, словари, наборы и т. Д.
В тот момент, когда все ссылки на объект исчезают, счетчик ссылок на объект падает до 0 и тут же удаляется.
Сборка мусора необходима только для прерывания циклических ссылок , объектов, которые ссылаются только друг на друга, без дальнейших ссылок на цикл. Поскольку такой цикл никогда не достигнет счетчика ссылок 0 без помощи, сборщик мусора периодически проверяет такие циклы и прерывает одну из ссылок, чтобы помочь очистить эти объекты из памяти.
Таким образом, вы можете заставить любой объект быть удаленным из памяти (освобожденным), удалив все ссылки на него. Как вы этого достигнете, зависит от того, как на объект ссылаются. Вы можете попросить переводчика сообщить, какие объекты ссылаются на данный объект с помощью функции gc.get_referrers()
, но учтите, что не дает вам имена переменных . Он предоставляет вам объекты, такие как объект словаря, который является атрибутом __dict__
модуля, который ссылается на объект как глобальный, и т. Д. Для кода, полностью находящегося под вашим контролем, самое большее, используйте gc.get_referrers()
как инструмент, чтобы напомнить себе, что места, на которые ссылается объект, когда вы пишете код для их удаления.
Если у вас должны быть уникальные идентификаторы для жизненного цикла Python-приложения , вам придется реализовать собственное средство. Если ваши объекты хешируемые и поддерживают слабые ссылки, то вы можете просто использовать WeakKeyDictionary
экземпляр , чтобы связать произвольные объекты с 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, они не вполне гарантируют уникальность, но вероятность того, что вы когда-либо встретите тот же идентификатор во время вашего Время жизни меньше, чем от удара метеорита. См. Насколько уникален UUID?
Затем вы получаете уникальные идентификаторы даже для объектов с неперекрывающимися временами жизни:
>>> 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