预计阅读本页时间:-
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))
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
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 map((lambda x: x * 2), range(N))
else:
@timer
def mapcall(N):
return list(map((lambda x: x * 2), range(N)))
...
Finally, as we learned in the modules part of this book if you want to be able to reuse this decorator in other modules, you should indent the self-test code at the bottom of the file under a __name__ == '__main__' test so it runs only when the file is run, not when it’s imported. We won’t do this, though, because we’re about to add another feature to our code.