On Tue, Jan 07, 2014 at 10:36:56PM -0500, Keith Winston wrote: > And yeah, I'd like to see your Stopwatch code... I haven't looked at "with" > yet, that's interesting. As usual, I don't totally get it...
Okay, let's talk about a common pattern in Python code: try: do some stuff finally: clean up You have a "try" block which runs some code. Whatever happens inside the block, regardless of whether it succeeds or fails, when it is done, Python runs the "finally" block. Let's see this in action, first with a successful computation, then an unsuccessful one: py> try: ... print(1 + 1) ... finally: ... print("=== Done ===") ... 2 === Done === py> try: ... print(1 + "1") ... finally: ... print("=== Done ===") ... === Done === Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str' This is such a common pattern, that a few versions back (Python 2.5, I believe) Python introduced new syntax for it: context managers and the "with" statement. The new syntax is: with obj as name: block When Python runs the "with" statement, it expects that obj will have two special methods: * obj.__enter__ is run when the "with" statement begins, just before the block is run; * obj.__exit__ is run when the block exits, regardless of whether it exited successfully, or due to an error. There are more complications, of course, but in a nutshell that's it. A with statement is not terribly different from this: name = obj.__enter__() try: block finally: obj.__exit__(*args) (The args passed to __exit__ have to do with any exceptions raised inside the block.) The Stopwatch code is quite simple: it has an __enter__ method which starts the timer, and an __exit__ method which stops it and reports how long things took. === cut === import gc import sys from functools import wraps class Stopwatch: """Time hefty or long-running block of code using a ``with`` statement: >>> with Stopwatch(): #doctest: +SKIP ... do_this() ... do_that() ... time taken: 1.234567 seconds The Stopwatch class takes four optional arguments: timer:: Timer function; by default, the default timer from the timeit module is used. allow_gc:: If true (the default), the garbage collector is free to operate while the code block is running, otherwise, the garbage collector is temporarily disabled. verbose:: If a true value (the default), the timer result is reported after the code block completes, and a warning displayed if the elapsed time is too small. cutoff:: If None, elapsed time warnings are disabled; otherwise, the amount of time in seconds below which a warning is displayed. Defaults to 0.001. For non-interactive use, you can retrieve the time taken using the ``interval`` attribute: >>> with Stopwatch(verbose=False) as sw: #doctest: +SKIP ... do_this() ... do_that() ... >>> print(sw.interval) #doctest: +SKIP 1.234567 """ def __init__(self, timer=None, allow_gc=True, verbose=True, cutoff=0.001): if timer is None: from timeit import default_timer as timer self.timer = timer self.allow_gc = allow_gc self.verbose = verbose self.cutoff = cutoff self.start = self.end = self.interval = None def __enter__(self): if not self.allow_gc: self._gc_state = gc.isenabled() gc.disable() self.interval = None self.start = self.timer() return self def __exit__(self, *args): self.end = self.timer() if not self.allow_gc and self._gc_state: gc.enable() self.interval = self.end - self.start self._report() def _report(self): if not self.verbose: return if self.cutoff is not None and self.interval < self.cutoff: print("elapsed time is very small; consider using timeit.Timer" " for micro-timings of small code snippets") print('time taken: %f seconds' % self.interval) === cut === And that's all there is to it! -- Steven _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor