Akihiko
Akihiko

Reputation: 372

Python: deletion of self referencing object

I want to ask how to delete an object with a self-reference in Python.

Let's think a class, which is a simple example to know when it is created and when it is deleted:

#!/usr/bin/python
class TTest:
  def __init__(self):
    self.sub_func= None
    print 'Created',self
  def __del__(self):
    self.sub_func= None
    print 'Deleted',self
  def Print(self):
    print 'Print',self

This class has a variable self.sub_func to which we assume to assign a function. I want to assign a function using an instance of TTest to self.sub_func. See the following case:

def SubFunc1(t):
  t.Print()
def DefineObj1():
  t= TTest()
  t.sub_func= lambda: SubFunc1(t)
  return t

t= DefineObj1()
t.sub_func()
del t

The result is:

Created <__main__.TTest instance at 0x7ffbabceee60>
Print <__main__.TTest instance at 0x7ffbabceee60>

that is to say, though we executed "del t", t was not deleted.

I guess the reason is that t.sub_func is a self-referencing object, so reference counter of t does not become zero at "del t", thus t is not deleted by the garbage collector.

To solve this problem, I need to insert

t.sub_func= None

before "del t"; in this time, the output is:

Created <__main__.TTest instance at 0x7fab9ece2e60>
Print <__main__.TTest instance at 0x7fab9ece2e60>
Deleted <__main__.TTest instance at 0x7fab9ece2e60>

But this is strange. t.sub_func is part of t, so I do not want to care about clearing t.sub_func when deleting t.

Could you tell me if you know a good solution?

Upvotes: 4

Views: 1722

Answers (2)

Dunes
Dunes

Reputation: 40703

How to makes sure an object in a reference cycle gets deleted when it is no longer reachable? The simplest solution is not to define a __del__ method. Very few, if any, classes need a __del__ method. Python makes no guarantees about when or even if a __del__ method will get called.

There are several ways you can alleviate this problem.

  1. Use a function rather than a lambda that contains and checks a weak reference. Requires explicit checking that the object is still alive each time the function is called.
  2. Create a unique class for each object so that we can store the function on a class rather than as a monkey-patched function. This could get memory heavy.
  3. Define a property that knows how to get the given function and turn it into a method. My personal favourite as it closely approximates how bound methods are created from a class'es unbound methods.

Using weak references

import weakref

class TTest:
    def __init__(self):
        self.func = None
        print 'Created', self
    def __del__(self):
        print 'Deleted', self
    def print_self(self):
        print 'Print',self

def print_func(t):
    t.print_self()

def create_ttest():
    t = TTest()
    weak_t = weakref.ref(t)
    def func():
        t1 = weak_t()
        if t1 is None:
            raise TypeError("TTest object no longer exists")
        print_func(t1)
    t.func = func
    return t

if __name__ == "__main__":
    t = create_ttest()
    t.func()
    del t

Creating a unique class

class TTest:
    def __init__(self):
        print 'Created', self
    def __del__(self):
        print 'Deleted', self
    def print_self(self):
        print 'Print',self

def print_func(t):
    t.print_self()

def create_ttest():
    class SubTTest(TTest):
        def func(self):
            print_func(self)
    SubTTest.func1 = print_func 
    # The above also works. First argument is instantiated as the object the 
    # function was called on.
    return SubTTest()

if __name__ == "__main__":
    t = create_ttest()
    t.func()
    t.func1()
    del t

Using properties

import types

class TTest:
    def __init__(self, func):
        self._func = func
        print 'Created', self
    def __del__(self):
        print 'Deleted', self
    def print_self(self):
        print 'Print',self
    @property
    def func(self):
        return types.MethodType(self._func, self)

def print_func(t):
    t.print_self()

def create_ttest():
    def func(self):
        print_func(self)
    t = TTest(func)
    return t

if __name__ == "__main__":
    t = create_ttest()
    t.func()
    del t

Upvotes: 4

unddoch
unddoch

Reputation: 6004

From the official CPython docs:

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.

See also: http://engineering.hearsaysocial.com/2013/06/16/circular-references-in-python/

Upvotes: 3

Related Questions