[Tutor] Generator next()

Steven D'Aprano steve at pearwood.info
Sat Dec 28 10:45:46 CET 2013


On Fri, Dec 27, 2013 at 01:14:42PM -0500, Keith Winston wrote:
> I am beginning to think about decorators, generators, and the like.
[...]
> Here's the code, stolen without apology from here:
> http://enja.org/2011/03/09/a-python-function-timing-decorator/
> 
> import time
> 
> class Timing(object):
>     def __init__(self):
>         self.timings = {}
>         self.col = self.__collector()
>         self.col.next() #coroutine syntax

[snip code]

Hmmm. I don't think much of that class. It seems to be over-complex, 
hard to use, and I'm not convinced that its timing results will even be 
accurate. I'm pretty sure that they won't be accurate for *small* 
functions, especially on Windows. They may be acceptably accurate for 
moderately large functions on Linux or Mac. I suppose that it's not a 
terrible way to instrument[1] a function, but I think there are much 
easier ways.

I don't think that its the best example of code to learn about 
decorators and generators! It is an advanced use of generators, namely 
coroutines, and its use as a decorator is at an intermediate level (it's 
a decorator factory rather than just a plain ol' decorator) and doesn't 
meet best-practice.

Anyway, if I'm going to criticise, I ought to show what I consider 
better. Here's a quick and unpolished decorator for instrumenting 
functions. For simplicity, it too will be inaccurate for Windows and 
really fast/small functions, but I reckon this is good enough for a 
first draft.


import functools
import time

def timer(function):
    """Add instrumentation to the decorated function. 

    The decorated function has three extra attributes:

        - count:  the number of times function has been called;
        - total:  total time elapsed when calling the function;
        - best:   the shortest time elapsed when called, or None
                  if the function hasn't been called yet.

    Times are measured in seconds.
    """
    @functools.wraps(function)
    def inner(*args, **kwargs):
        t = time.time()
        result = function(*args, **kwargs)
        elapsed = time.time() - t
        inner.count += 1
        inner.total += elapsed
        if inner.best is None:
            inner.best = elapsed
        else:
            inner.best = min(elapsed, inner.best)
        return result
    inner.count = 0
    inner.total = 0.0
    inner.best = None
    return inner



And here it is in use:

py> @timer
... def spam(x, y):
...     if x == y:
...         return "x equals y"
...     else:
...         return "x doesn't equal y"
...
py> spam(23, 42)
"x doesn't equal y"
py> spam(None, "hello")
"x doesn't equal y"
py> spam(2.5, 2.5)
'x equals y'
py> spam.count
3
py> spam.total
2.4557113647460938e-05
py> spam.best
7.62939453125e-06


That's my idea of an instrumented function :-)

Feel free to ask for a explanation of how it works.




[1] When did "instrument" become a verb???

-- 
Steven


More information about the Tutor mailing list