Как происходит переиспользование памяти у tuple в Python
Что проверяют
Отличаете ли вы гарантии языка от оптимизаций CPython и понимаете ли, почему на is для кортежей нельзя полагаться. Следом обычно просят объяснить, чем кортеж дешевле списка и почему равные кортежи иногда оказываются одним объектом, а иногда нет.
Как отвечать
Назовите конкретные механизмы, все они относятся к CPython:
- free list: при удалении небольшого кортежа память не возвращается аллокатору, объект кладётся в список свободных по своей длине. Следующий кортеж той же длины берётся оттуда без выделения памяти. Кешируются кортежи длиной примерно до 20 элементов, до 2000 штук на каждую длину;
- пустой кортеж: единственный объект на весь интерпретатор,
()всегда один и тот же; - константы в байткоде: кортеж из литералов компилятор сворачивает в константу и хранит в
co_consts. Функция сx = (1, 2, 3)при каждом вызове получает тот же объект, а не создаёт новый; - отсутствие копий:
tuple(t),t[:]иcopy.copy(t)возвращают исходный объект, копировать неизменяемое незачем; - компактность: кортеж хранит ссылки прямо в своём объекте и не держит запас под рост, как список;
- сборщик мусора: кортеж только из атомарных значений вроде чисел и строк CPython может перестать отслеживать в циклическом GC, это видно через
gc.is_tracked().
Важно проговорить границу: всё это детали реализации. Спецификация не обещает, что два равных кортежа будут одним объектом, а размеры free list меняются между версиями.
Где спотыкаются
Говорят, что кортежи интернируются, как строки. Интернирования для кортежей нет: tuple([1, 2]) is tuple([1, 2]) даёт False. Совпадение объектов у равных значений бывает у констант одного блока кода, и то как оптимизация.
Сравнивают кортежи через is. Результат зависит от того, где создан объект, и отличается в REPL и в файле. Для значений нужен ==.
Считают кортеж глубоко неизменяемым. В ([], 1) нельзя переприсвоить элемент по индексу, но вложенный список меняется, и такой кортеж даже не хешируется.