Наверняка, многие из вас, когда изучали питон читали, что
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 функцию
id2. Получаем указатель на объект
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 | Чат |









