☰ Оглавление

Утечки памяти в Python

Проблема утечек памяти в Python

Программисты на Perl знают, как легко вызывать утечку памяти на этом языке. Достаточно создать циклическую ссылку. Этот код течёт:

while(1) {
  my @p;
  push @p, \\@p;
}

Аналогичный код на Python не вызывает проблем:

# всё хорошо
while True:
    x = []
    x.append(x)

Это происходит потому, что в Python существует достаточно полноценный сборщик мусора, а не только механизм подсчёта ссылок. Этот сборщик запускается периодически и всё подчищает.

В этом примере тоже утечек нет:

# тоже хорошо
class X:
    pass
while True:
    x = X()
    x.prop = x

Но достаточно добавить метод __del__ и сборщик мусора перестаёт работать, даже если __del__ ничего не делает:

# ТЕЧЁТ
class X:
    def __del__(self):
        pass
while True:
    x = X()
    x.prop = x

Идеальный сферический пожиратель памяти в вакууме выглядит так:

# ТЕЧЁТ
class eat:
    def __init__(self):
        self.prop = self
    def __del__(self):
        pass
while True:
    eat()

Утечки происходят потому, что Python не знает, используется циклическая ссылка (в нашем случае это self.prop) в методе __del__. То есть, сборщик мусора не знает в каком месте надо начинать рвать цикл: или (i) сперва удалить ссылку self.propself, или (ii) сперва удалить ссылку selfself.prop?

Решение проблемы

Решить проблему можно в таком ключе:

# уже не течёт
import gc
class eat:
    def __init__(self):
        self.prop = self
        # Код, занимающийся очисткой циклических ссылок.
        # Форсируем сборку мусора. Каждый раз это делать
        # не обязательно, здесь это сделано только для
        # полноты. В боевых условиях Python сам запускает
        # сборку в подходящие моменты.
        gc.collect()
        # пробегаем по все объектам, которые не удалось
        # удалить, но доступа к которым уже нет
        for t in gc.garbage:
            # Строго говоря, тут надо бы поаккуратней
            # проверять типы. Но в нашем простом случае,
            # мы точно знаем, в чём причина проблем.
            # Рвём цикл.
            t.prop = None
        # Отсюда наши объекты тоже надо удалить. Не забываем.
        del gc.garbage[:]
    def __del__(self):
        pass
while True:
    eat()

Успехов!