id() в питоне не выдает уникальные значения Наверняка, многие из вас… — Находки в опенсорсе — TG.ME

id() в питоне не выдает уникальные значения

Наверняка, многие из вас, когда изучали питон читали, что id(obj) дает уникальный идентификатор объекта. Его даже назвали, блин, id 🌚️

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

Что вообще такое id?

Сначала, давайте посмотрим на то, как id() устроен:


static PyObject *
builtin_id_impl(PyModuleDef *self, PyObject *v)
{
PyObject *id = PyLong_FromVoidPtr(v);

if (id && PySys_Audit("builtins.id", "O", id) < 0) {
Py_DECREF(id);
return NULL;
}

return id;
}


Что тут происходит?
1. Объявляем C builtin функцию id
2. Получаем указатель на объект v, id которого хотим узнать
3. Получаем число из указателя (???)
4. Вызываем событие аудита для id
5. Возвращает результат (или NULL вместе с исключением)

Вроде бы все понятно, кроме пункта 3. Как можно из указателя получить обычный int?

Сначала разберемся с указателем в C.


int x = 0;
int *y = &x;


Здесь x - значение в ячейке памяти.
А *y - указатель на ячейку x. *y хранит в себе числовой адрес оригинальной ячейки.
Указывает на оригинальное значение в памяти. Как в известном меме.

По сути - работает как одна lea инструкция из x86_64.
https://godbolt.org/z/9xco3j8Y4

Возвращаемся к нашей C-API функции:


PyObject *
PyLong_FromVoidPtr(void *p)
{
// тут еще есть код унификации разных размерностей для разных платформ, но я его выкинул для простоты
return PyLong_FromUnsignedLongLong((unsigned long long)(uintptr_t)p);
}


А что за касты?
- uintptr_t - числовой тип, в который гарантировано поместится любой адрес
- unsigned long long - достаточно большой числовой тип, который поддерживает C-API

Вот так мы и получили int из указателя. Потому что указатель и есть числовое значение изначально.

Так почему не уникальный?

Потому что ячейки памяти можно и нужно переиспользовать!
Простой пример:


x, y, z = 1, 2, 3
t1 = (x, y, z)
id1 = id(t1)
del t1

a, b, c = 4, 5, 6
t2 = (a, b, c)
assert id(t2) == id1


Создаем кортеж, сохраняем его id, доводим его счетчик ссылок до 0, он удаляется, создаем новый такого же размера. Другой объект, другие значения. Получаем такой же id. Пу-пу-пу.

Почему так? Потому что в питоне есть freelist оптимизация: мы не выкидываем участки памяти под контейнеры частых размеров, а просто переиспользуем ту же память под новые контейнеры. Потому что аллокация памяти - очень дорогая. Одинаковый участок памяти = одинаковый адрес указателя = одинаковый id.

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

Обсуждение: Узнали ли вы что-то новое сегодня? Какие еще части питона вы хотели бы разобрать?

| Поддержать | YouTube | GitHub | Чат |
Python documentation
Audit events table
This table contains all events raised by sys.audit() or PySys_Audit() calls throughout the CPython runtime and the standard library. These calls were added in 3.8 or later (see PEP 578). See sys.ad...
2👍172🔥35❤22🤯9😁4👌3🤡2💩1🕊1
July 31, 2026 12.5K 79 102