TGViewer
Находки в опенсорсе Находки в опенсорсе @opensource_findings · 12.6K subscribers
Post #978 13.2K
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...
  • 👍 173
  • 🔥 36
  • ❤ 23
  • 🤯 9
  • 😁 4
  • 👌 3
  • 🤡 2
  • 💩 1
  • 🕊 1
More from @opensource_findings
  1. Sep 15, 2026PEPы в Python окончательно вышли из-под контроля Давайте посмотрим, что происходит с ПЕПам…
  2. Sep 10, 2026Большая бесплатная конференция в Нижнем Новгороде 17 октября Регистрация: https://itgorky.…
  3. Sep 1, 2026Post #985
  4. Aug 24, 2026JIT могут удалить из CPython! PEP-836: https://peps.python.org/pep-0836 JIT в CPython имее…
  5. Aug 18, 2026Как AWS у опенсорсера пакеты отжимал Нерегулярная рубрика "посмотрите, что творится!". Дан…
  6. Aug 14, 2026Какие уникальные фичи есть в django-modern-rest? Иногда, когда я добавляю какие-то фичи в…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →