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