Создание и удаление объектов. Сборщик мусора

Итого, в python все данные представляются в виде объектов, доступ к которым осуществляется или через имена/ссылки/идентификаторы в исходном коде программы.

Создание объектов

Создаются объекты в результате вычисления выражений python. Рассмотрим следующую строку кода.

x = 1001 + 1002

Когда интерпретатор исполняет исходный код, он сначала вычисляет выражение справа от оператора равно.

1001 + 1002

Чтобы его вычислить, сначала вычисляются подвыражения 1001 и 1002, оба из которых являются литералами целых чисел, что приводит к созданию целочисленных объектов с значениями 1001 и 1002.

Затем интерпретатор видит, что к этим целочисленным объектами необходимо применить операцию сложения, что приводит к созданию ещё одного целочисленного объекта с значением 2003.

print(x)
2003

Далее имя x связывается с эти объектом. Итого, в результате вычисления этой строки кода была создано 3 объекта. Но как объекты в python удаляются?

Прежде чем объяснить принцип удаления объектов из памяти, рассмотрим ключевой механизм для этого процесса.

Механизм подсчета ссылок на объект

Python автоматически следит за тем, сколько ссылок существует ссылок на каждый объект в любой момент времени. Для этого каждый объект имеет специальное целочисленное поле, которое используется в качестве счетчика ссылок на этот объект. Каждый раз, когда на объект появляется новая ссылка, этот счетчик увеличивается на единицу.

Продемонстрировать этот эффект можно с помощью функции getrefcount из модуля стандартной библиотеки sys, которая как раз и возвращает количество ссылок на объект.

Note

Важно учесть, что когда мы передаем объект на вход этой функции, создаётся временная ссылка на этот объект внутри неё, что приводит к тому, что она возвращает значение на 1 больше, чем можно было ожидать.

Т.к. python проводит ряд оптимизация при работе с неизменяемыми объектами, то для чистоты эксперимента создадим списковый объект, свяжем его с именем a и выведем количество ссылок на него.

from sys import getrefcount

a = []
print(getrefcount(a))
2

Видим, что количество ссылок на этот объект равняется двум: одна из этих ссылок — имя a, другая — временная ссылка, созданная внутри функции getrefcount при передаче этого объекта в качестве аргумента.

Создадим ещё пару ссылок на этот объект и снова напечатаем количество ссылок на список.

b = a
print(getrefcount(a))
c = a
print(getrefcount(a))
3
4

Видим, что счетчик вырастает на 1, каждый раз, когда мы связываем новое имя с исходным объектом.

Кроме имен программы на объект могут ссылаться другие объект (или даже тот же самый в предельном случае). Например, список хранит в себе ссылки на свои элементы.

L = [a, a, a]
print(getrefcount(a))
7

Созданный список L=[a, a, a] содержит 3 ссылки на исходный список, что отражается на увеличении счетчика ссылок.

Также временные ссылки создаются при передаче объекта в качестве аргумента функции. Именно этим и объясняется увеличенное значение, возвращаемое функцией getrefcount.

Удаление объектов в python. Ключевое слово del

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

Чтобы счетчик ссылок обнулился, необходимо чтобы пропали все ссылки на этот объект. Такое может происходить по разным причинам. Например, если связать какое-то существующее в программе имя с новым объектом, то пропадёт одна ссылка на объект, на которое ссылалось это имя прежде.

b = 0
print(getrefcount(a))
6

Видим, что связывание имени b с целочисленным объектом 0 привело к тому, что количество ссылок на пустой список упало на 1.

Кроме того, можно убирать просто удалить имя из программы, а вместе с ним и ссылку к объекту, на которое оно ссылается. Для этого используется ключевое слово del и синтаксис

del name

Удалим имя c и убедимся, что количество ссылок упадёт ещё на 1.

del c
print(getrefcount(a))
5

Локальные для функции имена удаляются автоматически при выходе из функции.

Если удаляется объект, то удаляются все его ссылки на другие объект. Чтобы продемонстрировать этот эффект, удалим список L и убедимся, что количество ссылок на a упадёт на 3.

del L
print(getrefcount(a))
2

Сборщик мусора

На первый взгляд может показаться, что механизма подсчета ссылок хватает самого по себе для удаления всех недостижимых объектов, но это не так. Может сложиться патологическая ситуация, когда группа объектов циклически ссылается друг на друга, но ни один из объектов этой группы нет ссылки снаружи. Такие объекты недостижимы для программы, а значит по хорошему их пора удалять, но на каждый из этих объектов все ещё существуют ссылки.

Воспроизведем такую ситуацию на примере двух списков.

L1 = []
L2 = []

L1.append(L2)
L2.append(L1)

print(L1, L2)
[[[...]]] [[[...]]]

Схема ниже иллюстрирует сложившуюся ситуацию.

../_images/cycle.png

Удалим изначальные ссылки L1 и L2.

del L1, L2

Теперь пропали все внешние ссылки на циклическую группу этих списков.

../_images/unreachable_cycle.png

Чтобы обнаруживать такие ситуации в python есть сборщик мусора, который представляет собой отдельный процесс, который переодически запускается и занимается поиском ненужных (недостижимых) объектов и удаляет их. Сборщик мусора в состоянии обнаружить недостижимые циклические структуры и удалить их.

Положительная сторона такого подхода заключается в том, что удаление объектов происходит полностью автоматически и у программиста нет необходимости удалять все объект самостоятельно. Отрицательная — накладные расходы: помимо интересующей пользователя программы параллельно работает дополнительный процесс, который может вызвать небольшие замедления в её работе.

Note

Модуль gc предоставляет интерфейс к взаимодействию с сборщиком мусора. В частности, можно отключить автоматическую сборку мусора, например, если производительность важнее раздувания памяти.

Note

Подробно прочитать о том, как работает сборщик мусора в python можно по ссылке в документации.