Python: как устроены идентификаторы объектов

Раздел: Python -> Управление памятью

Идентификаторы объектов в Python

Каждый объект в Python имеет уникальный идентификатор (целое число), получаемый с помощью функции id(). Этот идентификатор остается неизменным на протяжении жизни объекта. Оператор is проверяет, ссылаются ли две переменные на один и тот же объект (имеют одинаковый идентификатор).

Основной способ: использование id() и is

Для получения идентификатора объекта применяется id(obj). Например:

a = [1, 2, 3]
b = a
print(id(a))   # 140293847383872 (пример)
print(id(b))   # 140293847383872
print(a is b)  # True

идентификаторах объектов python (идентификаторы объектов в python)

Объяснение:

Переменные a и b ссылаются на один объект списка. Их идентификаторы совпадают, оператор is возвращает True. При изменении списка через a изменения видны через b.

Когда использовать:

  • Проверка, является ли переменная None (рекомендуется x is None).
  • Сравнение синглтонов (например, True, False, None).
  • Отладка: отслеживание, какие объекты разделяются между переменными.

Как определить, что две ссылки относятся к одному объекту?

Использование оператора is является предпочтительным способом, поскольку он работает быстрее, чем сравнение через id, и не требует дополнительного вызова функции.

x = 256
y = 256
print(x is y)  # True (маленькие целые кешируются)
z = 257
w = 257
print(z is w)  # False (в CPython обычно, но не гарантировано)

Python память переменная (память переменной в python)

Типичная ошибка: полагаться на идентичность для неизменяемых объектов.

Неизменяемые объекты (целые числа, строки, кортежи) могут быть кешированы интерпретатором. Маленькие целые от -5 до 256 кешируются, строки могут интернироваться. Поэтому a is b для двух одинаковых строк может дать True или False в зависимости от способа создания. Не следует использовать is для сравнения значений строк или чисел, если нет уверенности в кешировании. Для сравнения значений предназначен оператор ==.

Почему id объекта может измениться после его создания?

Идентификатор объекта остается неизменным на протяжении его жизни. Если объект уничтожен сборщиком мусора, а затем создан новый объект, он может получить тот же идентификатор. Пример:

a = [1,2,3]
print(id(a))
del a
b = [4,5,6]
print(id(b))  # может совпасть с id(a)

управление памяти python (управление памятью в python)

Это не изменение id, а переиспользование адреса памяти.

Проблема: путаница с изменчивостью id при переприсваивании.

Если переменной присвоить новый объект, ее идентификатор изменится, так как теперь она ссылается на другой объект. Но сам идентификатор предыдущего объекта остается прежним.

s = "hello"
print(id(s))
s = "world"
print(id(s))  # другой id, так как новый объект

Как проверить, что два разных объекта имеют одинаковое содержимое, но разные идентификаторы?

Для сравнения значений применяется оператор ==, а для проверки идентичности - is. Пример:

a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)  # True (содержимое одинаково)
print(a is b)  # False (разные объекты)

Как получить идентификатор объекта в шестнадцатеричном формате?

Можно использовать форматирование:

obj = object()
print(hex(id(obj)))  # '0x7f9e1c2b3a40'

Как проверить, что объект является синглтоном None?

Стандартная практика: x is None. Функция id(None) возвращает один и тот же адрес для всех ссылок на None.

a = None
b = None
print(id(a) == id(b))  # True
print(a is b)          # True

Как использовать id для отслеживания утечек памяти?

Сохранение идентификаторов объектов в словарь может помочь увидеть, сколько разных объектов создано. Однако этот способ ненадежен из-за переиспользования id после сборки мусора. Более надежным является модуль weakref.

ids = {}
def track(obj):
    ids[id(obj)] = obj
    return obj

a = track([1,2,3])
print(ids)  # {id: [1,2,3]}

Проблема: сборщик мусора может удалить объект, id станет доступен для нового объекта, что приведет к коллизии в словаре.

Расширенные примеры работы с идентификаторами

Пример 1: Кеширование маленьких целых чисел

Пример
# Целые от -5 до 256 кешируются
for i in range(-5, 258):
    a = i
    b = i
    if a is not b:
        print(f"{i}: a is b? {a is b}")
        break
else:
    print("Все числа от -5 до 256 являются одним объектом")
# Результат: "Все числа от -5 до 256 являются одним объектом"
Все числа от -5 до 256 являются одним объектом

Пример 2: Интернирование строк с sys.intern

Пример
import sys
s1 = "hello world"
s2 = "hello world"
print(s1 is s2)  # False (обычно, если строка не интернирована)
s1_intern = sys.intern(s1)
s2_intern = sys.intern(s2)
print(s1_intern is s2_intern)  # True
False
True

Пример 3: Идентификаторы вложенных изменяемых объектов

Пример
inner = [1, 2]
outer = [inner, inner]  # обе ссылки на один объект
print(id(outer[0]) == id(outer[1]))  # True
outer[0].append(3)
print(outer)  # [[1, 2, 3], [1, 2, 3]]
True
[[1, 2, 3], [1, 2, 3]]

Пример 4: id и сборщик мусора

Пример
import gc
gc.collect()  # принудительная сборка
id1 = id([1])
# удаляем последнюю ссылку
del _
id2 = id([2])
print(id1 == id2)  # может быть True, если память переиспользована
True (возможно)

Пример 5: Использование id для создания уникальных ключей

Пример
class Cache:
    def __init__(self):
        self._store = {}
    def get(self, obj):
        key = id(obj)
        return self._store.get(key)
    def set(self, obj, value):
        self._store[id(obj)] = value
# Примечание: такой кэш будет держать ссылки на объекты, предотвращая их сборку.
# Более правильным решением является использование WeakKeyDictionary.

Пример 6: Сравнение id и is для синглтонов

Пример
class Singleton:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

a = Singleton()
b = Singleton()
print(id(a) == id(b))  # True
print(a is b)          # True
True
True

Пример 7: Опасность использования id для сравнения в многопоточном коде

Пример
import threading
# id может быть переиспользован после удаления объекта в другом потоке
# поэтому сравнение по id не потокобезопасно. Для хранения данных потока следует использовать threading.local

Пример 8: id для объектов с переопределенным __eq__

Пример
class Custom:
    def __eq__(self, other):
        return True  # все объекты равны

a = Custom()
b = Custom()
print(a == b)  # True
print(a is b)  # False (разные идентификаторы)
True
False

Идентификаторы объектов в Python - comments

En
идентификаторах объектов python (python)