Предполагается, что вы уже умеете писать простые программы на Python и знаете базовый синтаксис (if, for, функции, списки). Здесь мы разбираем не как пользоваться языком, а почему он работает именно так.
В предыдущей статье мы выяснили, что оператор del удаляет не объект, а только имя.
Но тогда возникает вполне закономерный вопрос.
Если объект продолжает существовать после удаления имени, то кто вообще решает, когда его можно удалить из памяти?
Попробуем разобраться.
Представим самый простой пример.
numbers = [1, 2, 3]
После выполнения этой строки существует один объект списка.
На него ссылается одно имя.
Схематично это выглядит так:
numbers ───► [1, 2, 3]
Теперь появляется ещё одно имя.
other = numbers
Получаем:
numbers ─┐
│
▼
[1, 2, 3]
▲
│
other ────┘
Два имени.
Один объект.
Теперь выполним:
del numbers
Что происходит?
Мы уже знаем ответ.
Имя исчезает.
Но объект остаётся.
[1, 2, 3]
▲
│
other ────┘
Получается очень простое правило.
Пока существует хотя бы одна ссылка на объект, этот объект продолжает жить.
Теперь удалим и второе имя.
del other
Теперь схема выглядит совсем иначе.
[1, 2, 3]
На объект больше никто не ссылается.
Его невозможно получить по имени.
Его невозможно изменить.
Им невозможно воспользоваться.
Он стал полностью недостижим для программы.
Именно в этот момент Python понимает, что объект больше никому не нужен.
Можно представить себе, что каждый объект постоянно отвечает на очень простой вопрос:
Есть ли хоть кто-нибудь, кто ещё может до меня добраться?
Если ответ “да”, объект продолжает существовать.
Если ответ “нет”, память можно освободить.
На первый взгляд кажется, что Python должен каждый раз обходить всю программу и искать, остались ли где-нибудь ссылки на объект.
К счастью, делать этого не нужно.
Python использует гораздо более простую идею.
Представьте, что рядом с каждым объектом лежит небольшой счётчик.
Каждый раз, когда появляется новая ссылка на объект, счётчик увеличивается.
Например:
numbers = [1, 2, 3]
Теперь счётчик равен:
[1, 2, 3]
ссылок: 1
После строки
other = numbers
он становится таким:
[1, 2, 3]
ссылок: 2
После
third = numbers
уже:
[1, 2, 3]
ссылок: 3
Если затем выполнить
del other
счётчик уменьшится:
[1, 2, 3]
ссылок: 2
После удаления ещё одного имени:
del numbers
останется:
[1, 2, 3]
ссылок: 1
И только когда исчезнет последняя ссылка, счётчик станет равен нулю.
[1, 2, 3]
ссылок: 0
Вот тогда Python понимает:
“Этот объект больше никому не нужен.”
И память можно освободить.
Такой способ управления памятью называется подсчётом ссылок (reference counting).
Это одна из фундаментальных идей, на которых построен CPython - самая распространённая реализация Python.
Не пугайтесь нового термина.
На самом деле мы уже несколько статей подряд пользовались этой моделью.
Просто теперь у неё наконец появилось название.
Может возникнуть вопрос.
Получается, что все проблемы уже решены?
Если Python умеет считать ссылки, зачем ему вообще нужны какие-то дополнительные механизмы очистки памяти?
К сожалению, существует одна неприятная ситуация, с которой простой подсчёт ссылок справиться не может.
Представьте два объекта, которые ссылаются друг на друга.
a = []
b = []
a.append(b)
b.append(a)
Если удалить оба имени,
del a
del b
объекты всё ещё будут хранить ссылки друг на друга.
Получается замкнутый круг.
Снаружи добраться до этих объектов уже невозможно.
Но их счётчики ссылок так и не станут равны нулю.
Неужели такая память останется занятой навсегда?
К счастью, нет.
Именно для таких ситуаций Python использует ещё один механизм, который называется сборщиком мусора (Garbage Collector).
О том, как он работает и зачем вообще понадобился, мы поговорим в следующей статье.