Я не понимаю это поведение питона __del__ - PullRequest
30 голосов
/ 24 мая 2011

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

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 было бы в деструкторе!

Спасибо

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

Ответы [ 7 ]

33 голосов
/ 24 мая 2011

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

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

16 голосов
/ 29 мая 2011

Я даю свой собственный ответ, потому что, хотя я ценю совет избегать __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__ - это ПЛОХОЙ .

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

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

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

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

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

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

Это может быть исключением из правила, но есть варианты использования, где использование weakref и __del__ - правильная реализация, ИМХО.

11 голосов
/ 24 мая 2011

Вместо 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
8 голосов
/ 24 мая 2011

del не звонит __del__

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

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

d3 -> d3.func -> d3

Python не обнаруживает это и поэтому не может сразу его очистить. И это не просто справочные циклы. Если исключением является throw, вы, вероятно, захотите вызвать деструктор. Тем не менее, 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

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

2 голосов
/ 13 июня 2013

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

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

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

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

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

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


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

    d.bark = d.bark_softly
    d.bark()
2 голосов
/ 29 мая 2011

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

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
0 голосов
/ 23 сентября 2017

Альтернативным решением для использования 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))
...