[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