Варианты использования для __del__

Каковы варианты использования python 3 для написания пользовательского метода __del__ или использования одного из stdlib 1? То есть, в каком сценарии он разумно безопасен и может сделать что-то, что трудно сделать без него?

По многим причинам (1 2 3 4 5 6), обычно рекомендуется избегать __del__ и вместо этого использовать контекстные менеджеры или выполнять очистку вручную:

  • __del__ не может быть вызван, если объекты живы на выходе intrepreter 2.
  • В точке ожидают, что объект может быть уничтожен, количество ссылок может фактически быть ненулевым (например, ссылка может выжить через кадр трассировки, удерживаемый вызывающей функцией). Это делает время разрушения гораздо более неопределенным, чем подразумевается простая непредсказуемость gc.
  • Сборщик мусора не может избавиться от циклов, если они включают более одного объекта с __del__
  • Код внутри __del__ должен быть написан супер тщательно:
    • атрибуты объектов, установленные в __init__, могут отсутствовать, так как __init__ может вызвать исключение;
    • исключения игнорируются (печатаются только на stderr);
    • globals могут быть недоступны.

Update:

PEP 442 значительно улучшил поведение __del__. Кажется, что мои баллы 1-4 все еще действительны?


Обновление 2:

Некоторые из лучших библиотек python охватывают использование __del__ в питоне post-PEP 442 (то есть, python 3.4+), Я предполагаю, что моя точка 3 больше не действует после PEP 442, а остальные точки принимаются как неизбежная сложность завершения объекта.


1 Я расширил вопрос только от написания пользовательского метода __del__, включив в него __del__ из stdlib.

2 Кажется, что __del__ всегда вызывается на выходе интерпретатора в более поздних версиях Cpython (есть ли у кого-нибудь встречный пример?). Однако для целей __del__ usablity не имеет значения: документы явно не дают никаких гарантий относительно этого поведения, поэтому нельзя полагаться (он может измениться в будущих версиях, и он может отличаться у не-CPython-переводчиков).

Ответ 1

Контекстные менеджеры (и try/finally) несколько более ограничительны, чем __del__. В общем, они требуют, чтобы вы структурировали свой код таким образом, чтобы ресурс ресурса, который вам нужен для свободного, не выходил за пределы одного вызова функции на некотором уровне в стеке вызовов, а не, скажем, привязывал его к жизни экземпляра класса, который может быть уничтожен в непредсказуемые времена и места. Обычно полезно ограничивать время жизни ресурсов одной областью, но иногда возникают случаи, когда этот шаблон неудобен.

Единственный случай, когда я использовал __del__ (кроме отладки, c.f. @MSeifert answer), предназначен для освобождения памяти, выделенной вне Python внешней библиотекой. Из-за дизайна библиотеки, которую я обертывал, было трудно избежать наличия большого количества объектов, содержащих указатели на выделенную кучу память. Использование метода __del__ для освобождения указателей было самым простым способом очистки, поскольку было бы нецелесообразно включать продолжительность каждого экземпляра в диспетчер контекста.

Ответ 2

Один вариант использования - отладка. Если вы хотите отслеживать время жизни конкретного объекта, удобно написать метод временный __del__. Его можно использовать для выполнения некоторых протоколирования или просто для print. Я использовал его несколько раз, особенно когда мне было интересно, когда и как удаляются экземпляры. Иногда бывает полезно знать, когда вы создаете и отбрасываете много временных экземпляров. Но, как я уже сказал, я использовал это только для удовлетворения моего любопытства или отладки.

Другой вариант использования - это подкласс класса, который определяет метод __del__. Иногда вы обнаруживаете класс, который вы хотите использовать для подкласса, но внутренняя часть требует, чтобы вы фактически переопределили __del__ для управления порядком, в котором экземпляр очищен. Это очень редко, потому что вам нужно найти класс с __del__, вы должны подклассифицировать его, и вам нужно ввести некоторые внутренние компоненты, которые на самом деле требуют вызова суперкласса __del__ точно в нужное время. Я на самом деле сделал это один раз, но я не помню, где и почему это было важно (возможно, я даже не знал об альтернативах, поэтому рассматривайте это как возможный вариант использования).

Когда вы переносите внешний объект (например, c, который не отслеживается Python), что действительно, действительно необходимо освободить, даже если кто-то "забывает" (я подозреваю, что многие люди просто опускают их специально!), чтобы использовать предоставленный вами менеджер контекста.


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

Если вам интересно, нужны ли вам эти [метаклассы], вы не делаете (люди, которые действительно нуждаются в них, знают с уверенностью, что они им нужны, и не нужно объяснять, почему).

Цитата (возможно) от Тима Петерса (я не нашел исходную ссылку).

Ответ 3

В одном случае, когда я всегда использую __del__, для закрытия объекта aiohttp.ClientSession.

Если вы этого не сделаете, aiohttp напечатает предупреждения о незакрытом сеансе клиента.