memory leak with dynamically defined functions?

Duncan Booth duncan at NOSPAMrcp.co.uk
Tue Jul 17 06:43:27 EDT 2001


Harald Kirsch <kirschh at lionbioscience.com> wrote in
news:yv2k818t3wk.fsf at lionsp093.lion-ag.de: 

>> > >>> def silliest_func():
>> > >>>     x = [0] * (2**10)
>> > >>>     def inner_silliest_func(x=x): pass
>> > >>>     return inner_silliest_func
>> > >>> 
>> > >>> blarg = {}
>> > >>> for i in range(2**13): blarg[i] = silliest_func()
>> 
>> This creates 2**13 references to one and the same object, namely
>> silliest_func which (I suspect) has just one reference to an object
>> inner_silliest_func. 
> 
> Should read: 
> This creates 2**13 references to one and the same object, namely
> inner_silliest_func.

Your understanding seems to be slightly flawed here. This actually
creates 1 reference to each of 2**13 objects. Each object is a function
called inner_silliest_func with a default argument, but every time you
call silliest_func you create a new object 'x' and a new function using 
that x as an argument.

> So it appears to me that inner functions and/or their default arguments
> are not getting properly collected in CPython.  If this is the case, it
> poses quite a problem for Mojo Nation, as we have relied heavily on the
> convenient closure- like feature of passing around references to
> dynamically defined functions with default arguments.

Is there a good reason why you cannot upgrade to Python 2.1? It appears 
that this problem is fixed in 2.1 as the following example shows:

--- begin refs.py ---
import weakref
class Tracker:
    def __init__(self):
        self.__trackedobjects = weakref.WeakValueDictionary()
        
    def trackLifetime(self, object, name=None):
        """Track the lifetime of the object. Adds it to a weak reference 
dictionary.
        At the end of the test the weak dictionary must be empty again."""

        name = name or repr(object)
        tracked = self.__trackedobjects
        tracked[(name, id(object))] = object

    def printStatus(self):
        print "Tracker is tracking %d objects" % len(self.__trackedobjects)

    def verifyTrackedObjects(self):
        tracked = self.__trackedobjects

        if len(tracked) != 0:
            names = []
            for (name, id) in tracked.keys():
                names.append(name)
            msg = "Objects not freed: " + ", ".join(names)
            assert len(tracked)==0, msg

tracker = Tracker()
tracker.printStatus()

def silliest_func():
     x = [0]
     def inner_silliest_func(x=x): pass
     tracker.trackLifetime(inner_silliest_func)
     return inner_silliest_func
 
blarg = {}
for i in range(10): blarg[i] = silliest_func()
tracker.printStatus()

del blarg
tracker.printStatus()
tracker.verifyTrackedObjects()
--- end ---
Which gives you the output:
Tracker is tracking 0 objects
Tracker is tracking 10 objects
Tracker is tracking 0 objects

If you comment out the line 'del blarg' you will get a list of unfreed 
objects.

-- 
Duncan Booth                                             duncan at rcp.co.uk
int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?



More information about the Python-list mailing list