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