Деструктор не вызывается в скрипте Python - PullRequest
0 голосов
/ 04 сентября 2018

Ниже находится модуль, который выполняется так, как я ожидал.

class Z():
    def Y(self):
        return
    def __del__(self):
        print('Z deleted.')
def W(v):
    class Form:
        def X(self):
            #v.Y()
            return  
    return
def U():
    t = Z()
    W(t)
U()

Запуск вышеуказанного модуля дает следующий вывод

Z deleted.

Когда я удаляю комментарий, как показано ниже, вывод не производится.

class Z():
    def Y(self):
        return
    def __del__(self):
        print('Z deleted.')
def W(v):
    class Form:
        def X(self):
            v.Y()
            return  
    return
def U():
    t = Z()
    W(t)
U()

Почему деструктор не называется?

Я запускаю этот модуль в следующей утилите. Операционная система - Windows 10 Pro, версия 1803, сборка ОС 17134.165

capture

1 Ответ

0 голосов
/ 05 сентября 2018

Сценарий, который вы написали, создает референсный цикл менее чем очевидно. Неочевидный цикл является результатом того, что объявления all class по своей природе являются циклическими, поэтому простое существование объявления class в W означает, что будет некоторый циклический мусор. Я не уверен, является ли это обязательным условием для всех интерпретаторов Python, но это определенно верно для реализации CPython (по крайней мере, от 2.7 до 3.6, я проверял интерпретаторы).

То, что зацикливается в вашем экземпляре Z и запускает наблюдаемое вами поведение, заключается в том, что вы используете v (который является ссылкой на экземпляр Z) с областью закрытия, когда объявляете Form.x как часть class декларация. Область закрытия означает, что до тех пор, пока существует class Form, определяемый вызовом W, закрытая переменная v (в конечном счете, экземпляр Z) будет оставаться активной.

Когда вы запускаете модуль с IDLE, он запускает модуль и выводит вас в интерактивную подсказку после выполнения кода из модуля, но Python все еще работает, поэтому он не выполняет никакой очистки глобальных или немедленно запустить циклический GC. Экземпляр Z будет , в конце концов будет очищен (по крайней мере, на CPython 3.4+), но циклический GC обычно запускается только после довольно большого количества выделений без соответствующих освобождений (хотя по умолчанию на моих интерпретаторах 700) это деталь реализации). Но этот сбор может занять произвольно долгое время (перед завершением работы интерпретатора выполняется окончательная очистка цикла, но кроме этого нет никаких гарантий).

Комментируя строку, ссылающуюся на v, вы больше не закрываете v, поэтому циклический class больше не поддерживает v живым, а v быстро очищается (на CPython интерпретатор с подсчетом ссылок в любом случае, без гарантий на Jython, PyPy, IronPython и т. д.) при исчезновении последней ссылки.

Если вы хотите принудительно выполнить очистку, после запуска модуля вы можете запустить следующее в получившейся интерактивной оболочке, чтобы принудительно выполнить очистку поколения 0:

>>> import gc
>>> gc.collect(0)  # Or just gc.collect() for a full cycle collection of all generations

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

...