Я не понимаю этого поведения python __del__

Может кто-нибудь объяснить, почему следующий код ведет себя так:

import types

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d1 = Dummy("d1")
del d1
d1 = None
print "after d1"

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, d2)
d2.func()
del d2
d2 = None
print "after d2"

d3 = Dummy("d3")
def func(self):
    print "func called"
d3.func = types.MethodType(func, d3)
d3.func()
d3.func = None
del d3
d3 = None
print "after d3"

Вывод (обратите внимание, что деструктор для d2 никогда не вызывается) - это (python 2.7)

delete d1
after d1
func called
after d2
func called
delete d3
after d3

Есть ли способ "исправить" код, чтобы вызвать деструктор без удаления добавленного метода? Я имею в виду, что лучшее место для размещения d2.func = None было бы в деструкторе!

Спасибо

[edit] Основываясь на первых нескольких ответах, я хотел бы пояснить, что я не спрашиваю о достоинствах (или их отсутствии) использования __del__. Я попытался создать самую короткую функцию, которая продемонстрировала бы то, что я считаю неинтуитивным. Я предполагаю, что круговая ссылка была создана, но я не знаю, почему. Если возможно, я хотел бы знать, как избежать круговой ссылки....

Ответ 1

Я предоставляю свой собственный ответ, потому что, хотя я ценю советы, чтобы избежать __del__, мой вопрос состоял в том, как заставить его работать правильно для предоставленного примера кода.

Краткая версия: В следующем коде используется weakref, чтобы избежать циклической ссылки. Я думал, что пробовал это, прежде чем публиковать вопрос, но, наверное, я сделал что-то неправильно.

import types, weakref

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"

Более длинная версия: Когда я разместил вопрос, я искал похожие вопросы. Я знаю, что вы можете использовать with вместо этого, и что преобладающее чувство состоит в том, что __del__ является BAD.

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

Соединение с базой данных, по-видимому, часто используется в качестве примера, который не работает с использованием with, так как обычно вам нужно оставить раздел кода, который создает соединение, и соединение закрывается в более управляемом событием (а не последовательный).

Если with не является правильным решением, я вижу две альтернативы:

  • Вы убедитесь, что __del__ работает (см. этот блог для лучшего описание использования weakref)
  • Вы используете модуль atexit для запуска обратного вызова, когда ваша программа закрывается. См. этот раздел, например.

Пока я пытался предоставить упрощенный код, моя реальная проблема более управляема событиями, поэтому with не является подходящим решением (with отлично подходит для упрощенного кода). Я также хотел избежать atexit, так как моя программа может быть длительной, и я хочу как можно скорее выполнить очистку.

Итак, в этом конкретном случае я считаю это лучшим решением для использования weakref и предотвращения циклических ссылок, которые препятствовали бы работе __del__.

Это может быть исключением из правила, но существуют варианты использования, в которых использование weakref и __del__ является правильной реализацией, IMHO.

Ответ 2

Вы не можете предположить, что __del__ когда-либо будет вызван - не стоит надеяться, что ресурсы автоматически освободятся. Если вы хотите, чтобы был освобожден ресурс (без памяти), вы должны сделать release() или аналогичный метод, а затем вызывать это явно (или использовать его в диспетчере контекстов ), как указано Thanatos в комментариях ниже).

По крайней мере, вы должны внимательно прочитать __del__ документацию, и тогда вам, вероятно, не стоит пытаться использовать __del__. (Также см. Документацию gc.garbage для других плохих вещей о __del__)

Ответ 3

Вместо del вы можете использовать оператор with.

http://effbot.org/zone/python-with-statement.htm

точно так же, как с объектами filetype, вы могли бы что-то вроде

with Dummy('d1') as d:
    #stuff
#d is guaranteed to be out of scope

Ответ 4

del не вызывает __del__

del в том, как вы используете, удаляет локальную переменную. __del__ вызывается, когда объект уничтожается. Python как язык не дает никаких гарантий относительно того, когда он уничтожит объект.

CPython как наиболее распространенная реализация Python, использует подсчет ссылок. В результате del часто будет работать так, как вы ожидаете. Однако это не будет работать в случае, если у вас есть эталонный цикл.

d3 -> d3.func -> d3

Python не обнаруживает это и поэтому сразу не очистит его. И это не только эталонные циклы. Если вы выбрали исключение, вы, вероятно, захотите еще раз называть своего деструктора. Однако Python, как правило, удерживает локальные переменные как часть трассировки.

Решение не зависит от метода __del__. Скорее, используйте диспетчер контекста.

class Dummy:
   def __enter__(self):
       return self

   def __exit__(self, type, value, traceback):
       print "Destroying", self

with Dummy() as dummy:
    # Do whatever you want with dummy in here
# __exit__ will be called before you get here

Это гарантированно работает, и вы даже можете проверить параметры, чтобы увидеть, обрабатываете ли вы исключение и делаете что-то другое в этом случае.

Ответ 5

Полный пример менеджера контекста.

class Dummy(object):
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        return self
    def __exit__(self, exct_type, exce_value, traceback):
        print 'cleanup:', d
    def __repr__(self):
        return 'Dummy(%r)' % (self.name,)

with Dummy("foo") as d:
    print 'using:', d

print 'later:', d

Ответ 6

Мне кажется, что реальная суть дела здесь:

добавление функций является динамическим (во время выполнения) и не известно заранее

Я чувствую, что то, что вам действительно нужно, - это гибкий способ привязки различных функций к объекту, представляющему состояние программы. Также известен как полиморфизм. Python делает это достаточно хорошо, а не путем установки/отсоединения, а путем создания экземпляров разных классов. Я предлагаю вам снова взглянуть на свою организацию класса. Возможно, вам нужно отделить основной, постоянный объект данных от объектов переходного состояния. Используйте парадигму has-a, а не is-a: при каждом изменении состояния вы либо обертываете основные данные в объект состояния, либо присваиваете новый объект состояния атрибуту ядра.

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

class LongRunning(object):
    def bark_loudly(self):
        print("WOOF WOOF")

    def bark_softly(self):
        pring("woof woof")


while True:
    d = LongRunning()
    d.bark = d.bark_loudly
    d.bark()

    d.bark = d.bark_softly
    d.bark()

Ответ 7

Альтернативным решением для использования weakref является динамическое связывание функции с экземпляром только тогда, когда оно вызывается путем переопределения __getattr__ или __getattribute__ в классе, чтобы возвращать func.__get__(self, type(self)) вместо просто func для функций связанный с экземпляром. Вот как ведут себя функции, определенные на классе. К сожалению (для некоторых случаев использования) python не выполняет ту же логику для функций, связанных с самим экземпляром, но вы можете изменить его для этого. У меня были подобные проблемы с дескрипторами, связанными с экземплярами. Производительность здесь, вероятно, не так хороша, как использование weakref, но это опция, которая будет прозрачно работать для любой динамически назначенной функции с использованием только встроенных встроенных python.

Если вы часто это делаете, вам может понадобиться пользовательский метакласс, который выполняет динамическое связывание функций уровня экземпляра.

Другой альтернативой является добавление функции непосредственно в класс, который затем правильно выполнит привязку при ее вызове. Для многих случаев использования это будет иметь некоторые головные боли: именно, правильные имена, ограничивающие функции, чтобы они не сталкивались. Идентификатор экземпляра может использоваться для этого, хотя, поскольку идентификатор в cPython не гарантируется уникальным в течение всего срока службы программы, вам нужно подумать об этом немного, чтобы убедиться, что он работает для вашего случая использования... в в частности, вам, вероятно, необходимо убедиться, что вы удаляете функцию класса, когда объект выходит из области видимости, и, следовательно, его адрес id/memory доступен снова. __del__ идеально подходит для этого:). Кроме того, вы можете очистить все методы, имена которых помещены в экземпляр при создании объекта (в __init__ или __new__).

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

class MyClass(object):
    def dynamic_func(self, func_name):
        return getattr(self, func_name).__get__(self, type(self))

    def call_dynamic_func(self, func_name, *args, **kwargs):
        return getattr(self, func_name).__get__(self, type(self))(*args, **kwargs)

    """
    Alternate without using descriptor functionality:
    def call_dynamic_func(self, func_name, *args, **kwargs):
        return getattr(self, func_name)(self, *args, **kwargs)
    """

Чтобы сделать это сообщение полным, я покажу ваш вариант weakref:

import weakref
inst = MyClass()
def func(self):
    print 'My func'
#  You could also use the types modules, but the descriptor method is cleaner IMO
inst.func = func.__get__(weakref.ref(inst), type(inst))