3

I would like to understand how object deletion works on python. Here is a very simple bunch of code.

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 would recognize that I'm running a python 2.x version. More specially, this code runs on a python 2.7.1 setup.

This code outputs the following:

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

Surprisingly, A object is not deleted. I can understand why, since the setattr statement in __init__ produces a circular reference. But this one seems to be easy to resolve.

Finally, this page, in python documentation (Supporting Cyclic Garbage Collection), show that it's possible to deal with this kind of circular reference.

I would like to know:

  • why I never go thru my __del__ method in A class?
  • if my diagnosis about circular reference is good, why my object subclass does not support cyclic garbage collection?
  • finally, how to deal with this kind of setattr if I really want to go thru __del__?

Note: In A if the setattr points to another method of my module, there's no problem.

ohe
  • 3,461
  • 3
  • 26
  • 50
  • I doubt the `setattr` has anything to do with it. You're storing a bound method, which stores the object. You might as well store it via regular assignment (`self.test = self._test`). Please try it out, and if it produces the same output, you can simplify the question. –  Mar 22 '12 at 17:34
  • oh yes, it produces the same thing. But, `self.test = self._test` calls the `__setattr__` properties of an object. Am I right? – ohe Mar 22 '12 at 17:36
  • It does, but `setattr` should do that too. I believe `setattr` even respects properties. –  Mar 22 '12 at 17:45

2 Answers2

1

Fact 1

Instance methods are normally stored on the class. The interpreter first looks them up in the instance __dict__, which fails, and then looks on the class, which succeeds.

When you dynamically set the instance method of A in __init__, you create a reference to it in the instance dictionary. This reference is circular, so the refcount will never go to zero and the reference counter will not clean A up.

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

Fact 2

The garbage collector is what Python uses to deal with circular references. Unfortunately, it can't handle objects with __del__ methods, since in general it can't determine a safe order to call them. Instead, it just puts all such objects in gc.garbage. You can then go look there to break cycles, so they can be freed. From the docs

gc.garbage

A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects). By default, this list contains only objects with __del__() methods. Objects that have __del__() methods and are part of a reference cycle cause the entire reference cycle to be uncollectable, including objects not necessarily in the cycle but reachable only from it. Python doesn’t collect such cycles automatically because, in general, it isn’t possible for Python to guess a safe order in which to run the __del__() methods. If you know a safe order, you can force the issue by examining the garbage list, and explicitly breaking cycles due to your objects within the list. Note that these objects are kept alive even so by virtue of being in the garbage list, so they should be removed from garbage too. For example, after breaking cycles, do del gc.garbage[:] to empty the list. It’s generally better to avoid the issue by not creating cycles containing objects with __del__() methods, and garbage can be examined in that case to verify that no such cycles are being created.

Therefore

Don't make cyclic references on objects with __del__ methods if you want them to be garbage collected.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • This is **very** annoying. Indeed, I always know how to break cycles. But I want to do that at object deletion and this is, in many case why we "override" the `__del__` statement (otherwise, why should I want to write this statement ;)). Indeed, I can look inside this `gc.garbage` list, break cycles, and delete objects by calling `del gc.garbage[:]` but, as I say, I found this .... annoying. Thanks for your smart and quick response. – ohe Mar 22 '12 at 18:24
  • @ohe I obviously don't know the specifics of your case, but I think the more natural solution would be to define some sort of `.finish()` method -- that _you_ call when you are done -- which breaks the cycles. – Katriel Mar 22 '12 at 18:42
0

You should read the documentation on the __del__ method rather carefully - specifically, the part where objects with __del__ methods change the way the collector works.

The gc module provides some hooks where you can clean this up yourself.

I suspect that simply not having a __del__ method here would result in your object being properly cleaned up. You can verify this by looking through gc.garbage and seeing if your instance of A is present.

Nick Bastin
  • 30,415
  • 7
  • 59
  • 78
  • First line of documentation: Called when the instance is about to be destroyed .... Circular references which are garbage are detected when the option cycle detector is enabled (it’s on by default), but can only be cleaned up if there are no Python-level `__del__()` methods involved. So putting a __del__ method at python level breaks the cycle detector? How can we deal with that? – ohe Mar 22 '12 at 17:52
  • ohe: my answer points you to the gc module `garbage` variable that you can operate on to break the cycle. – Nick Bastin Mar 22 '12 at 17:55