Online Book Reader

Home Category

Learning Python - Mark Lutz [538]

By Root 1684 0
and so is probably a second choice here; it may be a useful coding pattern in other contexts, though.

In the rest of this chapter we’re going to be fairly casual about using classes or functions to code our function decorators, as long as they are applied only to functions. Some decorators may not require the instance of the original class, and will still work on both functions and methods if coded as a class—something like Python’s own staticmethod decorator, for example, wouldn’t require an instance of the subject class (indeed, its whole point is to remove the instance from the call).

The moral of this story, though, is that if you want your decorators to work on both simple functions and class methods, you’re better off using the nested-function-based coding pattern outlined here instead of a class with call interception.

Timing Calls

To sample the fuller flavor of what function decorators are capable of, let’s turn to a different use case. Our next decorator times calls made to a decorated function—both the time for one call, and the total time among all calls. The decorator is applied to two functions, in order to compare the time requirements of list comprehensions and the map built-in call (for comparison, also see Chapter 20 for another nondecorator example that times iteration alternatives like these):

import time

class timer:

def __init__(self, func):

self.func = func

self.alltime = 0

def __call__(self, *args, **kargs):

start = time.clock()

result = self.func(*args, **kargs)

elapsed = time.clock() - start

self.alltime += elapsed

print('%s: %.5f, %.5f' % (self.func.__name__, elapsed, self.alltime))

return result

@timer

def listcomp(N):

return [x * 2 for x in range(N)]

@timer

def mapcall(N):

return map((lambda x: x * 2), range(N))

result = listcomp(5) # Time for this call, all calls, return value

listcomp(50000)

listcomp(500000)

listcomp(1000000)

print(result)

print('allTime = %s' % listcomp.alltime) # Total time for all listcomp calls

print('')

result = mapcall(5)

mapcall(50000)

mapcall(500000)

mapcall(1000000)

print(result)

print('allTime = %s' % mapcall.alltime) # Total time for all mapcall calls

print('map/comp = %s' % round(mapcall.alltime / listcomp.alltime, 3))

In this case, a nondecorator approach would allow the subject functions to be used with or without timing, but it would also complicate the call signature when timing is desired (we’d need to add code at every call instead of once at the def), and there would be no direct way to guarantee that all list builder calls in a program are routed through timer logic, short of finding and potentially changing them all.

When run in Python 2.6, the output of this file’s self-test code is as follows:

listcomp: 0.00002, 0.00002

listcomp: 0.00910, 0.00912

listcomp: 0.09105, 0.10017

listcomp: 0.17605, 0.27622

[0, 2, 4, 6, 8]

allTime = 0.276223304917

mapcall: 0.00003, 0.00003

mapcall: 0.01363, 0.01366

mapcall: 0.13579, 0.14945

mapcall: 0.27648, 0.42593

[0, 2, 4, 6, 8]

allTime = 0.425933533452

map/comp = 1.542

Testing subtlety: I didn’t run this under Python 3.0 because, as described in Chapter 14, the map built-in returns an iterator in 3.0, instead of an actual list as in 2.6. Hence, 3.0’s map doesn’t quite compare directly to a list comprehension’s work (as is, the map test takes virtually no time at all in 3.0!).

If you wish to run this under 3.0, too, use list(map()) to force it to build a list like the list comprehension does, or else you’re not really comparing apples to apples. Don’t do so in 2.6, though—if you do, the map test will be charged for building two lists, not one.

The following sort of code would pick fairly for 2.6 and 3.0; note, though, that while this makes the comparison between list comprehensions and map more fair in either 2.6 or 3.0, because range is also an iterator in 3.0, the results for 2.6 and 3.0 won’t compare directly:

...

import sys

@timer

def listcomp(N):

return [x * 2 for x in range(N)]

if sys.version_info[0] == 2:

@timer

def mapcall(N):

Return Main Page Previous Page Next Page

®Online Book Reader