Метод __del__ вызывается в Python, когда он не ожидается - PullRequest
6 голосов
/ 20 декабря 2009

Я новичок в python и работаю с примерами из Swaroop CH "A Byte of Python". Я вижу какое-то поведение с методом __del__, которое меня озадачивает.

Обычно, если я запускаю следующий скрипт (в Python 2.6.2)

class Person4:
    '''Represents a person'''
    population = 0

    def __init__(self, name):
        '''Initialize the person's data'''
        self.name = name
        print 'Initializing %s'% self.name

        #When the person is created they increase the population
        Person4.population += 1

    def __del__(self):
        '''I am dying'''
        print '%s says bye' % self.name

        Person4.population -= 1

        if Person4.population == 0:
            print 'I am the last one'
        else:
            print 'There are still %d left' % Person4.population


swaroop = Person4('Swaroop')
kaleem = Person4('Kalem')

с помощью консоли Python (или интерактивной консоли Spyder) я вижу следующее:

ExecFile (u'C: \ 1_eric \ Python \ test1.py ')
Инициализация Swaroop
Инициализация Калем

ExecFile (u'C: \ 1_eric \ Python \ test1.py ')
Инициализация Swaroop
Swaroop говорит пока
Я последний
Инициализация Калем
Калем говорит пока
Я последний

Почему метод __del__ вызывается сразу после __init__ при втором запуске?
Я предполагаю, что поскольку используются одни и те же имена экземпляров («swaroop» и «kaleem»), он освобождает исходный экземпляр и собирает мусор. Но, похоже, это наносит ущерб текущему количеству населения.

Что здесь происходит?
Как можно избежать такого рода путаницы?
Избегать использования __del__? Проверить существующие имена экземпляров перед их повторным использованием? ...

Спасибо, Eric

Ответы [ 2 ]

19 голосов
/ 20 декабря 2009

Здесь происходит несколько вещей. Когда создается экземпляр вашего класса Person4, он инициализирует свою переменную класса population значением 0. С вашей интерактивной консоли вы, похоже, запускаете файл "test1.py" несколько раз. Во второй раз, когда вы запускаете его, класс Person4 объявляется снова, что делает его технически отличным от первого (даже если он имеет то же имя). Это означает, что он имеет свой независимый population счет.

Теперь swaroop и kaleem являются глобальными переменными, общими для обоих ваших экземпляров "test1.py". Python внутренне использует подсчет ссылок для большей части своей автоматической сборки мусора, поэтому исходный экземпляр первого Person4 класса не освобождается до второго присваивания swaroop. Присвоение swaroop уменьшает счетчик ссылок для первого экземпляра, вызывая вызов __del__, поскольку счетчик ссылок теперь равен нулю. Но поскольку вы ссылаетесь на Person4 по имени внутри __del__(), когда предыдущий экземпляр исчезает, он уменьшает счетчик new Person4.population вместо старого Person4 количество населения.

Надеюсь, это имело смысл. Я понимаю, почему это может сбивать с толку тех, кто изучает Python. Использование переменных класса одновременно с переопределением класса Person4 с использованием execfile() еще более запутывает вопросы. Что бы это ни стоило, я написал много кода на Python и не думаю, что мне когда-либо приходилось использовать специальный метод __del__.

7 голосов
/ 20 декабря 2009

Общий совет: не используйте __ del __ в Python. Это может сломать сборку мусора несколькими способами, особенно в случае циклических ссылок между объектами.

В вашем примере существуют различные проблемы, связанные с использованием execfile (), что не является наилучшей практикой, и переопределением глобальных переменных. Кстати, если вам действительно нужно создать псевдодеструктор (т. Е. Код, который вызывается всякий раз, когда объект получает мусор), напишите так называемую функцию «финализатор» (это не совсем деструктор) и вызовите ее с помощью weakref обратный вызов. Конечно, это НЕ должен быть метод экземпляра, и помните, что лямбда на самом деле создает замыкание, поэтому убедитесь, что в обратном вызове не пропущена любая ссылка на себя! Если вам нужны данные из уничтоженного экземпляра, используйте подход аргумента func по умолчанию, просто убедитесь, что никогда не ссылается на «self» внутри лямбды, иначе это не будет работать.

from weakref import ref
from time import sleep

class Person4:
    '''Represents a person'''
    population = 0

    def __init__(self, name):
        '''Initialize the person's data'''
        self.name = name
        print 'Initializing %s'% self.name

        #When the person is created they increase the population
        Person4.population += 1

        self._wr = ref(self, lambda wr, name=self.name: Person4_finalizer(name))

def Person4_finalizer(name):
        '''I am dying'''
        print '%s says bye' % name

        Person4.population -= 1

        if Person4.population == 0:
            print 'I am the last one'
        else:
            print 'There are still %d left' % Person4.population

p1 = Person4("one")
p2 = Person4("two")
p3 = Person4("three")

del p2
del p3
sleep(5)

вывод (сон помогает увидеть, что происходит):

Initializing one
Initializing two
Initializing three
two says bye
There are still 2 left
three says bye
There are still 1 left
one says bye
I am the last one
...