Decorator question: prefer class, but only function works

Russell E. Owen rowen at uw.edu
Mon Nov 14 20:00:38 EST 2011


In article 
<CALwzidk11rqXjaxcwNKy5C2iotaBO_BcDWL_zFC6Rctue=4U-A at mail.gmail.com>,
 Ian Kelly <ian.g.kelly at gmail.com> wrote:

> On Thu, Nov 10, 2011 at 2:52 PM, Russell E. Owen <rowen at uw.edu> wrote:
> > I am trying to write a decorator that times an instance method and
> > writes the results to a class member variable. For example:
> >
> > def timeMethod(func):
> >    def wrapper(self, *args, **keyArgs):
> >        t1 = time.time()
> >        res = func(self, *args, **keyArgs)
> >        duration = time.time() - t1
> >        self.timings[func.__name__] = duration
> >        return res
> >    return wrapper
> >
> > This works, but I'm not very happy with the way self.timings is obtained.
> 
> What do you feel is wrong with it? 

Oops, I stripped so much out of my example that I stripped the ugly bit. 
This is closer to the original and demonstrated the issue:

def timeMethod(func):
    name = func.__name__ + "Duration"
    def wrapper(self, *args, **keyArgs):
       t1 = time.time()
       res = func(self, *args, **keyArgs)
       duration = time.time() - t1
       self.timings[name] = duration
       return res
   return wrapper

I don't like the way name is passed into wrapper. It works, but it looks 
like magic. A class offers an obvious place to save the information. Or 
I could just generate the name each time.

I realize I'm showing the limits of my understanding of python binding 
of variable names, but I also think that if I find it confusing then 
others will, as well.

> sum(os.times()[:2]) instead, which (assuming your script is
> single-threaded) will more accurately count the actual CPU time spent
> in the function rather than real time, which could be quite different
> if the CPU is busy.

Thanks for the suggestion. I decided to use time.clock(), which I 
understand gives basically the same information (at a resolution that is 
sufficient for my needs).

> Also, why do you need this?  If you're just trying to evaluate the
> speed of your code, you should consider using a proper profiler or the
> timeit module. The former will tell you how much time is spent in
> each function, while the latter runs the code a large number of times
> in a loop, which gives you better precision for quick methods.

It is for timing stages of a data processing pipeline. Only long-running 
tasks will be timed. Repeatedly running to get better times is neither 
practical nor necessary to get a good feeling of where the time is being 
spent.

> > I first tried to write this as a class (for readability), and this did
> > NOT work:
> >
> > class timeMethod(object):
> >    def __init__(self, func):
> >        self.func = func
> >    def __call__(self, *args, **keyArgs):
> >        t1 = time.time()
> >        res = self.func(*args, **keyArgs)
> >        duration = time.time() - t1
> >        args[0].timings.set(self.func.__name__, duration)
> >        return res
> >
> > In the first case the wrapper is called as an unbound function, so it
> > works. But in the second case the wrapper is called as a bound method --
> > thus args[0] is not func's class instance, and I can't get to the
> > timings attribute.
> 
> I prefer the function version myself, but to make this work you could
> add something like this (untested) to make your wrapper class a
> descriptor that tacks on the self argument:
> 
>     def __get__(self, instance, owner):
>         from functools import partial
>         if instance is None:
>             return self
>         return partial(self, instance)

Thank you very much. I'll stick to the function, since it works, but 
it's nice to know how to work around the problem.




More information about the Python-list mailing list