4

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?

Akihiko
  • 362
  • 3
  • 14
  • Why do you care about this? Can you use a [weakref](https://docs.python.org/2/library/weakref.html)? – BrenBarn Dec 23 '14 at 21:32
  • 3
    The Python garbage collector can detect cycles. In years of python programming, I've never used a weak reference, and I've **very rarely** needed an explicit delete. Are you sure you need to? Do you have a memory problem? – salezica Dec 23 '14 at 21:33
  • Perhaps call `gc.collect()` after `del`? – Reut Sharabani Dec 23 '14 at 21:35
  • Thank you guys. I tested gc.collect() but it didn't work. My python version is 2.7.5+. Actually, I relied on GC of python so far, but I realized that some my objects I wanted to be released were not released because of this problem. Some of them are using large memory. I expected such an object is released at the end of a function, but it remains, so I need to rewrite every code, which is weird. – Akihiko Dec 23 '14 at 21:44
  • I haven't considered weakref yet. – Akihiko Dec 23 '14 at 21:52
  • I'm not 100% sure, but I think that python won't garbage-collect cycles where the objects have custom `__del__` methods. – unddoch Dec 23 '14 at 21:52
  • Best is to design your software to not use circular references at all. If you must, then use weak references. – Keith Dec 23 '14 at 21:53

2 Answers2

4

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
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • Thank you for the three ideas. I like the first one (using weak reference) the most because it is less code, easy to understand (it's close to a pointer of C/C++), and intuitive. I think, in my case, the **explicit checking of the object existence** is not necessary since "func" is always executed as "t.func" which means t (and its weak ref) exists. – Akihiko Dec 24 '14 at 20:40
3

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/

unddoch
  • 5,790
  • 1
  • 24
  • 37
  • Year, technically to say, my problem seems to be a sort of circular references. But its actual meaning is obviously different. There is a hierarchy in the problematic part: t.sub_func= lambda: SubFunc1(t); SubFunc1(t) is a sub element of t. The problem is that python can't know this hierarchy. Do you think using weakref is the proper way to tell this to python? – Akihiko Dec 23 '14 at 22:22
  • Do you need your `__del__` method? Very few classes do. If none of the objects involved in a cycle have an explicit `__del__`, Python can garbage collect them just fine. Its only when at least one `__del__` method exists that the interpreter refuses to collect the cycle, since it can't guess what parts are needed by the `__del__` method itself. – Blckknght Dec 23 '14 at 23:15