Из документации по питону:

Не гарантируется, что методы __del__() вызываются для объектов, которые все еще существуют на момент выхода из интерпретатора.

Почему бы и нет? Какие проблемы возникнут, если эта гарантия была сделана?

16
Shea Levy 31 Янв 2013 в 18:46

5 ответов

Лучший ответ

Я не убежден предыдущими ответами здесь.

Во-первых, обратите внимание, что приведенный пример не предотвращает вызов методов __del__ во время выхода. Фактически, текущие CPythons будут вызывать данный метод __del__, дважды в случае Python 2.7 и один раз в случае Python 3.4. Так что это не может быть «убийственным примером», который показывает, почему гарантия не предоставляется.

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

Вместо этого я думаю, что это утверждение просто отражает тот факт, что реализация CPython иногда не вызывает все деструкторы при выходе (предположительно для простоты реализации).

Кажется, что ситуация в том, что CPython 3.4 и 3.5 всегда вызывают все деструкторы при выходе из интерпретатора.

CPython 2.7, напротив, не всегда делает это. Конечно, методы __del__ обычно не вызываются для объектов, имеющих циклические ссылки, поскольку эти объекты нельзя удалить, если у них есть метод __del__. Сборщик мусора не будет собирать их. Хотя объекты действительно исчезают при выходе из интерпретатора (конечно), они не завершаются, и поэтому их методы __del__ никогда не вызываются. Это больше не верно в Python 3.4 после реализации PEP 442.

Однако кажется, что Python 2.7 также не завершает объекты, которые имеют циклические ссылки, даже если у них нет деструкторов, если они становятся недоступными только при выходе из интерпретатора.

Предположительно, такое поведение достаточно специфично и трудно объяснить, что его лучше всего выразить просто общим отказом от ответственности - как это делают документы.

Вот пример:

class Foo(object):
    def __init__(self):
        print("Foo init running")

    def __del__(self):
        print("Destructor Foo")

class Bar(object):
    def __init__(self):
        print("Bar1 init running")
        self.bar = self
        self.foo = Foo()

b = Bar()

# del b

С закомментированным del b деструктор в Foo не вызывается в Python 2.7, хотя в Python 3.4.

После добавления del b деструктор вызывается (при выходе из интерпретатора) в обоих случаях.

7
strubbly 8 Мар 2016 в 08:31

Если бы вы сделали несколько неприятных вещей, вы могли бы оказаться с необратимым объектом, который python попытался бы удалить навсегда:

class Phoenix(object):
    def __del__(self):
        print "Deleting an Oops"
        global a
        a = self

a = Phoenix()

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

4
mgilson 31 Янв 2013 в 15:09

Вероятно, потому, что большинство программистов предполагают, что деструкторы должны вызываться только для мертвых (уже недоступных) объектов, а здесь при выходе мы будем вызывать их для живых объектов.

Если разработчик не ожидал вызова деструктора для живого объекта, это может привести к неприятным последствиям. По крайней мере, что-то должно быть сделано для принудительного закрытия приложения после истечения времени ожидания, если оно зависает. Но тогда некоторые деструкторы не могут быть вызваны.

Java Runtime.runFinalizersOnExit устарел по той же причине.

0
h22 31 Янв 2013 в 14:55

Один из примеров, где деструктор не вызывается, если вы выходите из метода. Посмотрите на этот пример:

class Foo(object):
    def __init__(self):
        print("Foo init running")

    def __del__(self):
        print("Destructor Foo")


class Bar(object):
    def __init__(self):
        print("Bar1 init running")
        self.bar = self
        self.foo = Foo()

    def __del__(self):
        print("Destructor Bar")

    def stop(self):
        del self.foo
        del self
        exit(1)

b = Bar()
b.stop()

Результат:

Bar1 init running
Foo init running
Destructor Foo

Поскольку мы явно уничтожаем foo, вызывается деструктор, но не деструктор bar!

И, если мы не удалим foo явно, он также не будет уничтожен должным образом:

class Foo(object):
    def __init__(self):
        print("Foo init running")

    def __del__(self):
        print("Destructor Foo")


class Bar(object):
    def __init__(self):
        print("Bar1 init running")
        self.bar = self
        self.foo = Foo()

    def __del__(self):
        print("Destructor Bar")

    def stop(self):
        exit(1)

b = Bar()
b.stop()

Выход:

Bar1 init running
Foo init running
1
sebix 12 Янв 2018 в 16:55

Я не думаю, что это потому, что удаление может вызвать проблемы. Более того, философия Python заключается не в том, чтобы поощрять разработчиков полагаться на использование удаления объектов, потому что время этих удалений не может быть предсказано - это зависит от сборщика мусора, когда это происходит.

Если сборщик мусора может отложить удаление неиспользуемых объектов на неопределенное время после того, как они выйдут за пределы области действия, то полагаться на побочные эффекты, возникающие при удалении объекта, не очень надежная или детерминистическая стратегия. RAII не является способом Python. Вместо этого код Python обрабатывает очистку, используя менеджеры контекста, декораторы и тому подобное.

Хуже того, в сложных ситуациях, таких как циклы объектов, сборщик мусора может никогда не обнаружить, что объекты могут быть удалены. Эта ситуация улучшилась, когда Python повзрослел. Но из-за исключений из ожидаемого поведения GC, подобных этому, для разработчиков Python неразумно полагаться на удаление объектов.

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

1
Jonathan Hartley 27 Апр 2018 в 17:58