Действительно ли возможно иметь фактическую утечку памяти в Python из-за Вашего кода?

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

47
задан orokusaki 23 April 2015 в 11:06
поделиться

4 ответа

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

Это зависит от того, о какой утечке памяти вы говорите. В чистом коде 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('$')

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

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

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

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

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

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

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

Чтобы решить эту проблему, убедитесь, что вы выполняете очистку, скажем, при отключении событий, вызывая своего рода close, но назовите этот метод как-нибудь иначе, чем __ 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 , являющееся пустым списком, означает добавление к нему, когда оно фактически ссылка на тот же список . Это опять же может вызвать неограниченный рост, даже если вы не подозреваете об этом.

108
ответ дан 7 November 2019 в 12:59
поделиться

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

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

15
ответ дан 7 November 2019 в 12:59
поделиться

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

11
ответ дан 7 November 2019 в 12:59
поделиться

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

0
ответ дан 7 November 2019 в 12:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: