From: John Reid on
Hi,

I've written a decorator that prints exceptions and I'm having some
trouble with garbage collection.

My decorator is:

import sys
def print_exception_decorator(fn):
def decorator(self, *args, **kwds):
try:
return fn(*args, **kwds)
except:
print 'Exception:', sys.exc_info()
raise
return decorator



The class I want to decorate the methods of is:

class InstanceCounted(object):
"A class that keeps track of how many instances there are."
count = 0
def __init__(self):
InstanceCounted.count += 1
def __del__(self):
InstanceCounted.count -= 1

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):
del self.method

def method(self):
pass



When I run the following it does not seem like my object 'a' is garbage
collected:

print 'Have %d instances' % InstanceCounted.count
print 'Creating A'
a = A()
print 'Have %d instances' % InstanceCounted.count
print 'Deleting A'
del a
print 'Have %d instances' % InstanceCounted.count


This is the output:

Have 0 instances
Creating A
Have 1 instances
Deleting A
Have 1 instances


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.

Can anyone suggest anything? Is my technique to decorate bound methods
not a good one? How else should I decorate a bound method?

Thanks in advance,
John.


From: Thomas Jollans on
On 06/23/2010 01:40 PM, John Reid wrote:
> Hi,
>
> I've written a decorator that prints exceptions and I'm having some
> trouble with garbage collection.
>
> My decorator is:
>
> import sys
> def print_exception_decorator(fn):
> def decorator(self, *args, **kwds):
> try:
> return fn(*args, **kwds)
> except:
> print 'Exception:', sys.exc_info()
> raise
> return decorator
>
>
>
> The class I want to decorate the methods of is:
>
> class InstanceCounted(object):
> "A class that keeps track of how many instances there are."
> count = 0
> def __init__(self):
> InstanceCounted.count += 1
> def __del__(self):
> InstanceCounted.count -= 1
>
> 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):
> del self.method
>
> def method(self):
> pass
>
>
>
> When I run the following it does not seem like my object 'a' is garbage
> collected:
>
> print 'Have %d instances' % InstanceCounted.count
> print 'Creating A'
> a = A()
> print 'Have %d instances' % InstanceCounted.count
> print 'Deleting A'
> del a
> print 'Have %d instances' % InstanceCounted.count
>
>
> This is the output:
>
> Have 0 instances
> Creating A
> Have 1 instances
> Deleting A
> Have 1 instances
>
>
> 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.

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.

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.

-- Thomas

>
> Can anyone suggest anything? Is my technique to decorate bound methods
> not a good one? How else should I decorate a bound method?
>
> Thanks in advance,
> John.
>
>

From: Peter Otten on
John Reid wrote:

> Hi,
>
> I've written a decorator that prints exceptions and I'm having some
> trouble with garbage collection.
>
> My decorator is:
>
> import sys
> def print_exception_decorator(fn):
> def decorator(self, *args, **kwds):
> try:
> return fn(*args, **kwds)
> except:
> print 'Exception:', sys.exc_info()
> raise
> return decorator
>
>
>
> The class I want to decorate the methods of is:
>
> class InstanceCounted(object):
> "A class that keeps track of how many instances there are."
> count = 0
> def __init__(self):
> InstanceCounted.count += 1
> def __del__(self):
> InstanceCounted.count -= 1
>
> 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):
> del self.method
>
> def method(self):
> pass
>
>
>
> When I run the following it does not seem like my object 'a' is garbage
> collected:
>
> print 'Have %d instances' % InstanceCounted.count
> print 'Creating A'
> a = A()
> print 'Have %d instances' % InstanceCounted.count
> print 'Deleting A'
> del a
> print 'Have %d instances' % InstanceCounted.count
>
>
> This is the output:
>
> Have 0 instances
> Creating A
> Have 1 instances
> Deleting A
> Have 1 instances
>
>
> 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.
>
> Can anyone suggest anything? Is my technique to decorate bound methods
> not a good one? How else should I decorate a bound method?

The problem is that cyclic garbage collection cannot cope with __del__()
methods. Quoting http://docs.python.org/library/gc.html#gc.garbage

"""
Objects that have __del__() methods and are part of a reference cycle cause
the entire reference cycle to be uncollectable
"""

The best workaround is to control the object's lifetime explicitly by
turning it into a context manager, see

http://docs.python.org/reference/datamodel.html#with-statement-context-
managers

or providing a close() method and use contextlib:

>>> class A(object):
.... def close(self):
.... print "break cycles here"
....
>>> class A(object):
.... def __init__(self):
.... self.self = self
.... def __del__(self):
.... print "bye"
.... def close(self):
.... print "breaking cycles here"
.... del self.self
....
>>> a = A()
>>> del a
>>> import contextlib
>>> with contextlib.closing(A()) as b:
.... print "using b"
....
using b
breaking cycles here
>>> del b
bye

Peter
From: Christian Heimes on
> 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__ introduces a whole bunch of new issues with cyclic gc.
http://docs.python.org/library/gc.html#gc.garbage

> Can anyone suggest anything? Is my technique to decorate bound methods
> not a good one? How else should I decorate a bound method?

You shouldn't use __del__ to count your instances. Instead you can use
sys.getrefcount() and gc.get_objects() or use a weakref with a callback.

Christian

From: John Reid on


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?

>
> 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.

 |  Next  |  Last
Pages: 1 2
Prev: example of multi threads
Next: Another MySQL Problem