On 07/13/2010 09:14 PM, Will Woods wrote:
> 2) ExceptionCatcher decorator
> -----------------------------
>
> [patch 0001 - lib/python/decorators.py]
>
> What the heck is this?!? Well, you remember, that the 'postprocess'
> function is called only if the run_once ends well (i.e. no exception is
> thrown from the run_once). This, of course, is absolutely not OK, if one
> wants to have some common "out" point - e.g. call the postprocess
function.
>
> This decorator then watches over the decorated function, and if
> exception is raised, the decorator stores it, calls the
> 'exception_happened' function (which is given as parameter to the
> decorator, see the docstring for more detail), and then re-raises the
> exception.
I definitely like the decorator idea, but I have some questions about
the implementation. It's interesting that you chose to implement this as
a class, rather than a function - although decorators-with-args are kind
of a brain-buster either way, really. Heh.
Well, i like classes more, if I ever find myself in need to adjust
stuff/add functionality, class gives me a better substructure to work
on, so it's a personal preference. Other than that, the
decorator-with-args always seemed to be more 'straightforward' maybe
while one tries to decipher the magic :)
As I understand from reading the code, it seems you want to pass a
class
method (like 'self.run_once_failed') to trigger if an exception is
raised, which is a good idea. But it seems like you could do this just
by doing, say:
def initialize_failed(self):
...
@ExceptionCatcher(initialize_failed)
def initialize(self, config, **kwargs):
self.config = config_loader(config, self.tmpdir)
...
i.e. pass the function directly, rather than using the tricky
manipulation of string-name-to-callable.
Given that the first argument will be a callable, it seems like you
could define the decorator in the more traditional way, something like
this:
def ExceptionCatcher(on_exception, *oe_args, **oe_kwargs):
'''docs go here'''
def _wrap(f): # this wraps the function we're decorating
def _inner(*args, **kwargs):
exc_info = None
try:
r = f(*args, **kwargs) # decorated function
except:
exc_info = sys.exc_info() # save exc info
finally:
if exc_info is not None:
on_exception(*oe_args, **oe_kwargs)
raise exc_info[1], None, exc_info[2]
return r
return _inner
return _wrap
(see
http://www.kylev.com/2009/05/28/a-brief-python-decorator-primer/
for examples of the traditional double-function way of doing decorators
with arguments)
Well, I understand what you're saying, but it has one tiny little
problem - you need to get the "self" reference in there. This is not a
big deal, really because you actually have the "self" as a first
parameter *args. But then we would need to decide, that the
"on_exception" method always has to be method of the respective class in
which the decorator was used (i'm not sure if it's clear, so lets shoot
some examples :)
Assumption: the decorated method is a class method, on_exception method
is a class method of the same class as the decorated method:
=====================
def ExceptionCatcher(on_exception, *oe_args, **oe_kwargs):
'''docs go here'''
def _wrap(f): # this wraps the function we're decorating
def _inner(*args, **kwargs):
exc_info = None
try:
r = f(*args, **kwargs) # decorated function
except:
exc_info = sys.exc_info() # save exc info
finally:
if exc_info is not None:
# <--- notice the args[0] ~ 'self'
on_exception(args[0], *oe_args, **oe_kwargs)
raise exc_info[1], None, exc_info[2]
return r
return _inner
return _wrap
##### (1) Only this will work #####
class MyClass(object):
def exc_handler(self):
print "ALL YOUR BASE ARE BELONG TO US!"
@ExceptionCatcher(exc_handler)
def failing(self):
raise Exception("I'm naughty method!")
#### (2) But we won't be ever able to do this ####
from my_awesome_library import my_exc_handler
class MyClass2(object):
@ExceptionCatcher(my_exc_handler)
def failing(self):
raise Exception("I'm naughty method!")
#### (3) Neither this #####
def standalone_catcher():
print "I'm Great!"
@ExceptionCatcher(standalone_catcher)
def foo(bar = 1):
raise Exception("I'd like to use the decorator outside classes")
=================================================
So the question basically is - is the __getattribute__ really so evil,
that we do want the decorator to be very single-targeted? If so, then I
do not really have a problem with the second approach, but it kind of
seemed more reasonable to have a generaly-working decorator at the cost
of maybe more 'hackish' code.
So these are my $.02 on the matter. Looking forward to your reply.
Would something like that work, or would that mess up autotest's
introspection?
Well, the introspection is broken once you use **kwargs somewhere in the
decorator (and decorators can not function without it), so the 'last arg
of the function has to be **kwargs when we decorate inside our tests'
still applies.
joza