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

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

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

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

x = 1001 + int("1002")

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

1001 + int("1002")

Так как это составное выражение, то, чтобы его вычислить, необходимо сначала вычислить результаты составляющих его подвыражений 1001 и int("1002").

  1. Первое из них (1001) является литералом целого числа 1001. Его интерпретация приводит к созданию целочисленного объекта со значением 1001.

  2. Второе выражение (int("1002")) тоже является составным.

    1. Сначала вычисляется выражение "1002", указанное в качестве аргумента функции int. Это выражение является литералом строки, а значит его интерпретация приводит к созданию строкового объекта со значением "1002".

    2. Далее этот объект передаётся на вход функции int, что приводит к созданию целочисленного объекта со значением 1002.

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

print(x)
2003

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

../../_images/expressions.png

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

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

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

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

Note

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

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

from sys import getrefcount

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

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

../../_images/refcount_1.png

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

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

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

../../_images/refcount_2.png

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

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

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

../../_images/refcount_3.png

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

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

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

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

b = 0
print(getrefcount(a))
6

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

../../_images/refcount_4.png

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

del name

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

del c
print(getrefcount(a))
5
../../_images/refcount_5.png

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

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

del L
print(getrefcount(a))
2
../../_images/refcount_6.png

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

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

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

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 предоставляет интерфейс к взаимодействию со сборщиком мусора. В частности, можно отключить автоматическую сборку мусора методом disable, например, если сценарий программы не подразумевает создание циклических структур, или если стабильная производительность программы важнее потенциальной утечки памяти. Кроме отключения сборщика мусора, с помощью этого модуля можно в ручную его запускать методом collect.

Note

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