Instance method decorator garbage collection problem

Thomas Jollans thomas at jollans.com
Wed Jun 23 13:53:39 EDT 2010


On 06/23/2010 02:56 PM, John Reid wrote:
> 
> 
> Thomas Jollans wrote:
>>> The InstanceCounted.count is 1 at the end. If I omit the call to
>>> "self.method = print_exception_decorator(self.method)" then the instance
>>> count goes down to 0 as desired. I thought that the decorator might be
>>> holding a reference to the instance through the bound method, so I added
>>>  the __del__() but it doesn't fix the problem.
>>
>> Adding __del__ like this does "fix the problem", but it introduces a new
>> one: lacking a call to super().__del__, you simply don't decrement the
>> instance count.
> 
> Now that's a good point! I've added super().__del__ but the problem
> remains for some reason. My A class now looks like:
> 
> class A(InstanceCounted):
>     "A class that I want to decorate a method on."
>     def __init__(self):
>         super(A, self).__init__()
>         self.method = print_exception_decorator(self.method)
> 
>     def __del__(self):
>         super(A, self).__del__()
>         del self.method
> 
>     def method(self):
>         pass
> 
> 
> Did you try this?

No, I didn't. And, as Peter Otten pointed out, it wouldn't work anyway.
But this will work:

##############

import sys
from functools import wraps

def print_exc(func):
    @wraps(func)
    def wrapper(*a, **kwa):
        try:
            return func(*a, **kwa)
        except:
            print 'Exception', sys.exc_info()
            raise
    return wrapper

class InstanceCounted(object):
    count = 0
    def __init__(self):
        type(self).count += 1

    def __del__(self):
        type(self).count -= 1

class TestClass(InstanceCounted):
    def __init__(self):
        print 'TestClass initialized'
        super(TestClass, self).__init__()

    @print_exc
    def testmethod(self):
        print "Heureka", self

if __name__ == '__main__':
    print '%d instances!' % TestClass.count
    TestClass()
    print '%d instances!' % TestClass.count
    t = TestClass()
    print '%d instances!' % TestClass.count
    t.testmethod()
    t2 = TestClass()
    print '%d instances!' % TestClass.count
    del t2
    print '%d instances!' % TestClass.count
    del t
    print '%d instances!' % TestClass.count


################

And the output is:

0 instances!
TestClass initialized
0 instances!
TestClass initialized
1 instances!
Heureka <__main__.TestClass object at 0x7f39a6fc2ad0>
TestClass initialized
2 instances!
1 instances!
0 instances!


> 
>>
>> To decorate a method, you'd best just decorate it normally. I doubt your
>> technique will work anyway, as the function returned by the decorator
>> isn't bound to the object, you'd need to pass one self reference
>> implicitly, which is then thrown away.
> 
> Looking at the original post, I had included an extra "self" in the
> argument list
> 
> def decorator(self, *args, **kwds):
> 
> should have been
> 
> def decorator(*args, **kwds):
> 
> 
> With this correction my method decorates instance methods on objects
> successfully although I don't know why the garbage collection isn't
> working.
> 
> 
> 
>>
>> simply,
>>
>> def exc_decor(fn):
>>     @functools.wraps(fn)
>>     def wrapper(*args, **keywords):
>>         try:
>>             return fn(*args, **keywords):
>>         except:
>>             #...
>>             raise
>>     return wrapper
>>
>> class X(...):
>>    @exc_decor
>>    def foo(self, arg):
>>       pass
>>
>> (if targeting pre-decorator Python, the code would look different of
>> course)
>>
>> This way, the function itself is decorated, and the function returned by
>> the decorator is bound to the object. It'll just work as expected, no
>> trickery required.
> 
> Thanks for this. I remember having some problems decorating instance
> methods in the past which is why I started doing it as in the original
> post. Your method seems just fine though.
> 
> Thanks,
> John.
> 




More information about the Python-list mailing list