setattr, удаление объектов и циклическая сборка мусора

Я хотел бы понять, как удаление объектов работает на python. Вот очень простой набор кода.

class A(object):

    def __init__(self):
        setattr(self, "test", self._test)

    def _test(self):
        print "Hello, World!"

    def __del__(self):
        print "I'm dying!"

class B(object):

    def test(self):
        print "Hello, World!"

    def __del__(self):
        print "I'm dying"

print "----------Test on A"
A().test()
print "----------Test on B"
B().test()

Pythonista узнает, что я использую версию python 2.x. В частности, этот код работает на установке Python 2.7.1.

Этот код выводит следующее:

----------Test on A
Hello, World!
----------Test on B
Hello, World!
I'm dying

Удивительно, но объект A не удаляется. Я могу понять почему, поскольку оператор setattr в __init__ создает циклическую ссылку. Но это, кажется, легко решить.

Наконец, эта страница в документации Python (поддержка циклической сборки мусора) показывает, что можно иметь дело с такого рода циклической ссылкой.

Я бы хотел знать:

  • почему я никогда не пользуюсь своим методом __del__ в классе A?
  • если мой диагноз циклической ссылки верен, почему мой подкласс object не поддерживает циклическую сборку мусора?
  • наконец, что делать с такого рода setattr, если я действительно хочу пройти через __del__?

Примечание. В A, если setattr указывает на другой метод моего модуля, проблем нет.


person ohe    schedule 22.03.2012    source источник
comment
Сомневаюсь, что setattr имеет к этому какое-то отношение. Вы сохраняете связанный метод, который хранит объект. Вы также можете сохранить его с помощью обычного присваивания (self.test = self._test). Пожалуйста, попробуйте, и если он выдаст тот же результат, вы можете упростить вопрос.   -  person    schedule 22.03.2012
comment
о да, он производит то же самое. Но self.test = self._test вызывает __setattr__ свойства объекта. Я прав?   -  person ohe    schedule 22.03.2012
comment
Так и есть, но setattr тоже должен это делать. Я считаю, что setattr даже уважает собственность.   -  person    schedule 22.03.2012


Ответы (2)


Факт 1

Методы экземпляра обычно хранятся в классе. Интерпретатор сначала ищет их в экземпляре __dict__, который дает сбой, а затем ищет класс, который завершается успешно.

Когда вы динамически устанавливаете метод экземпляра A в __init__, вы создаете ссылку на него в словаре экземпляра. Эта ссылка является циклической, поэтому счетчик ссылок никогда не станет равным нулю, а счетчик ссылок не будет очищаться A.

>>> class A(object):
...     def _test(self): pass
...     def __init__(self):
...             self.test = self._test
... 
>>> a = A()
>>> a.__dict__['test'].im_self

Факт 2

Сборщик мусора — это то, что Python использует для работы с циклическими ссылками. К сожалению, он не может обрабатывать объекты с __del__ методами, так как вообще не может определить безопасный порядок их вызова. Вместо этого он просто помещает все такие объекты в gc.garbage. Затем вы можете посмотреть туда, чтобы разорвать циклы, чтобы их можно было освободить. Из документов.

gc.garbage

Список объектов, которые сборщик обнаружил недоступными, но не смог освободить (несобираемые объекты). По умолчанию этот список содержит только объекты с __del__() методами. Объекты, имеющие __del__() методов и являющиеся частью ссылочного цикла, делают весь ссылочный цикл недоступным для сбора, включая объекты, не обязательно находящиеся в цикле, но достижимые только из него. Python не собирает такие циклы автоматически, потому что, как правило, Python не может угадать безопасный порядок запуска методов __del__(). Если вы знаете безопасный порядок, вы можете вызвать проблему, изучив список мусора и явно разорвав циклы из-за ваших объектов в списке. Обратите внимание, что даже в этом случае эти объекты остаются живыми благодаря тому, что они находятся в списке мусора, поэтому их также следует удалить из мусора. Например, после прерывания циклов выполните del gc.garbage[:], чтобы очистить список. Как правило, лучше избежать этой проблемы, не создавая циклы, содержащие объекты с __del__() методами, и в этом случае можно проверить garbage, чтобы убедиться, что такие циклы не создаются.

Следовательно

Не делайте циклических ссылок на объекты с __del__ методами, если вы хотите, чтобы они были удалены сборщиком мусора.

person Katriel    schedule 22.03.2012
comment
Это очень раздражает. Действительно, я всегда знаю, как разорвать циклы. Но я хочу сделать это при удалении объекта, и во многих случаях именно поэтому мы переопределяем оператор __del__ (иначе зачем мне писать этот оператор ;)). Действительно, я могу заглянуть внутрь этого списка gc.garbage, разорвать циклы и удалить объекты, вызвав del gc.garbage[:], но, как я уже сказал, я нашел это.... раздражающим. Спасибо за умный и быстрый ответ. - person ohe; 22.03.2012
comment
@ohe Я, очевидно, не знаю специфики вашего случая, но я думаю, что более естественным решением было бы определить какой-то метод .finish() - который вы вызываете, когда закончите, - который ломает циклы. - person Katriel; 22.03.2012

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

В модуле gc есть несколько зацепок, которые вы можете почистить самостоятельно.

Я подозреваю, что простое отсутствие здесь метода __del__ приведет к правильной очистке вашего объекта. Вы можете убедиться в этом, просмотрев gc.garbage и увидев, присутствует ли ваш экземпляр A.

person Nick Bastin    schedule 22.03.2012
comment
Первая строка документации: вызывается, когда экземпляр вот-вот будет уничтожен.... Циклические ссылки, которые являются мусором, обнаруживаются, когда включен детектор циклов опций (он включен по умолчанию), но могут быть очищены только при отсутствии Python задействованы методы уровня __del__(). Таким образом, использование метода del на уровне Python нарушает работу детектора циклов? Как мы можем справиться с этим? - person ohe; 22.03.2012
comment
ohe: мой ответ указывает вам на переменную garbage модуля gc, с которой вы можете работать, чтобы разорвать цикл. - person Nick Bastin; 22.03.2012