Возможно ли иметь реальную утечку памяти в Python из-за вашего кода? - PullRequest
45 голосов
/ 07 января 2010

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

Ответы [ 6 ]

103 голосов
/ 07 января 2010

Возможно, да.

Это зависит от того, о какой утечке памяти вы говорите. В чистом коде Python невозможно «забыть освободить» память, такую ​​как в C, но можно оставить ссылку где-нибудь висящей. Некоторые примеры таких:

необработанный объект трассировки, поддерживающий весь кадр стека, даже если функция больше не работает

while game.running():
    try:
        key_press = handle_input()
    except SomeException:
        etype, evalue, tb = sys.exc_info()
        # Do something with tb like inspecting or printing the traceback

В этом глупом примере игрового цикла, возможно, мы присвоили 'tb' локальному. У нас были благие намерения, но этот tb содержит информацию о фрейме стека всего, что происходило в нашем handle_input вплоть до того, что это вызывало. Предполагая, что ваша игра продолжается, этот «tb» сохраняется даже при следующем вызове handle_input и, возможно, навсегда. Документы для exc_info теперь говорят об этой потенциальной проблеме циклических ссылок и рекомендуют просто не назначать tb, если она вам абсолютно не нужна. Если вам нужно получить трассировку, подумайте, например, traceback.format_exc

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

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

class Money(object):
    name = ''
    symbols = []   # This is the dangerous line here

    def set_name(self, name):
        self.name = name

    def add_symbol(self, symbol):
        self.symbols.append(symbol)

В приведенном выше примере, скажем, вы сделали

m = Money()
m.set_name('Dollar')
m.add_symbol('$')

Вы, вероятно, быстро найдете эту конкретную ошибку, но в этом случае вы помещаете изменяемое значение в область видимости класса, и даже если вы правильно обращаетесь к нему в области видимости экземпляра, оно фактически "проваливается" в объект класса s __dict__.

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

Циклические ссылки в классах, которые также имеют метод __del__.

Как ни странно, существование __del__ делает невозможным циклический сборщик мусора для очистки экземпляра. Скажем, у вас было что-то, что вы хотели сделать деструктором для целей финализации:

class ClientConnection(...):
    def __del__(self):
        if self.socket is not None:
            self.socket.close()
            self.socket = None

Теперь это прекрасно работает само по себе, и вы можете быть уверены, что это хороший управляющий ресурсами ОС для обеспечения того, чтобы сокет был «утилизирован».

Однако, если ClientConnection сохранил ссылку, скажем, User, а Пользователь сохранил ссылку на соединение, вы можете испытать искушение сказать, что при очистке, давайте попросим пользователя прекратить ссылку на соединение. Однако на самом деле это недостаток : циклический ГХ не знает правильный порядок операций и не может его очистить.

Решение этой проблемы - убедиться, что вы выполняете очистку, скажем, отключаете события, вызывая какое-то закрытие, но называете этот метод чем-то отличным от __del__.

плохо реализованные расширения C или неправильно используемые библиотеки C, как они должны быть.

В Python вы доверяете сборщику мусора выбрасывать вещи, которые вы не используете. Но если вы используете расширение C, которое упаковывает библиотеку C, большую часть времени вы несете ответственность за обеспечение явного закрытия или отмены выделения ресурсов. В основном это задокументировано, но программист на python, который привык не делать этого явного перераспределения, может отбросить дескриптор (например, возвращение из функции или чего-то еще) этой библиотеке, не зная, что ресурсы удерживаются.

Области, которые содержат замыкания, которые содержат намного больше, чем вы могли бы ожидать

class User:
    def set_profile(self, profile):
        def on_completed(result):
            if result.success:
                self.profile = profile

        self._db.execute(
            change={'profile': profile},
            on_complete=on_completed
        )

В этом надуманном примере мы, похоже, используем какой-то асинхронный вызов, который перезвонит нам на on_completed, когда вызов БД завершен (реализация могла быть обещанной, она заканчивается тем же результат).

Что вы можете не осознавать, так это то, что замыкание on_completed связывает ссылку с self для выполнения назначения self.profile. Теперь, возможно, клиент БД отслеживает активные запросы и указатели на замыкания для вызова, когда они сделаны (так как это асинхронно), и сообщает, что происходит сбой по любой причине. Если клиент БД неправильно очищает обратные вызовы и т. Д., В этом случае клиент БД теперь имеет ссылку на on_completed, которая имеет ссылку на пользователя, которая хранит _db - вы создали циклическую ссылку, которая может никогда не получить собраны.

(Даже без циклической ссылки тот факт, что замыкания связывают локальные и даже экземпляры, иногда может привести к тому, что значения, которые, как вы думали, были собраны, долго жили, что может включать сокеты, клиенты, большие буферы и целые деревья )

Параметры по умолчанию, которые являются изменяемыми типами

def foo(a=[]):
    a.append(time.time())
    return a

Это надуманный пример, но можно предположить, что значение по умолчанию a, являющееся пустым списком, означает добавление к нему, когда это фактически ссылка на тот же список . , Это снова может привести к неограниченному росту, не зная, что вы это сделали.

15 голосов
/ 07 января 2010

Классическим определением утечки памяти является память, которая использовалась один раз, а теперь нет, но не была исправлена. Это почти невозможно с чистым кодом Python. Но, как отмечает Антуан, вы можете легко непреднамеренно потреблять всю вашу память, позволяя структурам данных расти без ограничений, даже если вам не нужно хранить все данные вокруг.

С расширением C, конечно, вы вернулись на неуправляемую территорию, и все возможно.

11 голосов
/ 07 января 2010

Конечно можно. Типичный пример утечки памяти - это создание кеша, который никогда не очищается вручную и не имеет политики автоматического удаления.

0 голосов
/ 28 января 2019

Я создаю объект с тяжелым атрибутом для демонстрации использования памяти процесса.

Затем я создаю словарь, который ссылается на себя много раз.

Затем я удаляю объект и прошу GC собрать мусор. Он не собирает ничего.

Затем я проверяю объем ОЗУ процесса - он такой же.

Ну вот, утечка памяти!

α python
Python 2.7.15 (default, Oct  2 2018, 11:47:18)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import gc
>>> class B(object):
...     b = list(range(1 * 10 ** 8))
...
>>>
[1]+  Stopped                 python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164   0.0 19.0  7562952 3188184 s010  T     2:08pm   0:03.78 /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
~/Sources/plan9port [git branch:master]
α fg
python

>>> b = B()
>>> for i in range(1000):
...     b.a = {'b': b}
...
>>>
[1]+  Stopped                 python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164   0.0 19.0  7579336 3188264 s010  T     2:08pm   0:03.79 /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
~/Sources/plan9port [git branch:master]
α fg
python


>>> b.a['b'].a
{'b': <__main__.B object at 0x109204950>}
>>> del(b)
>>> gc.collect()
0
>>>
[1]+  Stopped                 python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164   0.0 19.0  7579336 3188268 s010  T     2:08pm   0:05.13 /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
0 голосов
/ 26 января 2019

Поскольку многие модули написаны на C, да, возможны утечки памяти. представьте, что вы используете контекст рисования с графическим интерфейсом (например, с помощью wxpython), вы можете создавать буферы памяти, но если вы забыли его освободить у вас будут утечки памяти ... в этом случае C ++ функции wx api переносятся в python.

большее неправильное использование, представьте, что вы перегружаете эти методы wx виджетов в python ... гарантированно утечка памяти.

0 голосов
/ 07 января 2010

В смысле потерянных выделенных объектов после того, как они выходят из области видимости, потому что вы забыли освободить их, нет; Python автоматически освобождает объекты вне области видимости ( Сборка мусора ). Но в том смысле, о котором говорит @Antione, да.

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