Подтвердить что ты не робот

Почему на выходе переводчика не могут быть вызваны деструкторы?

Из python docs:

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

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

4b9b3361

Ответ 1

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

Во-первых, обратите внимание, что в приведенном примере не предотвращается вызов методов __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 деструктор вызывается (при выходе интерпретатора) в обоих случаях.

Ответ 2

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

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

a = Phoenix()

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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