""" tracer function decorator examples run me under 3.0 to make the 3rd example execute """ # add return, **, 3.0 print() class tracer: def __init__(self, func): # on @ decorator self.calls = 0 # save func for later call self.func = func def __call__(self, *args, **kwargs): # on call to original function self.calls += 1 print('call %s to %s' % (self.calls, self.func.__name__)) return self.func(*args, **kwargs) if __name__ == '__main__': @tracer def spam(a, b, c): # same as: spam = tracer(spam) print(a + b + c) # triggers tracer.__init__ @tracer def eggs(x, y): # same as: eggs = tracer(eggs) print(x ** y) # wraps eggs in a tracer object spam(1, 2, 3) # really calls tracer instance: runs tracer.__call__ spam(a=4, b=5, c=6) # spam is an instance attribute eggs(2, 16) # really calls tracer instance, self.func is eggs eggs(4, y=4) # self.calls is per-function here (need 3.0 nonlocal) print('') ################################################################### # enclosing scopes, global shared by all wrapped functions calls = 0 def tracer(func): # state via nested scope and global def wrapper(*args, **kwargs): # instead of class attributes global calls # calls is global, not per-function calls += 1 print('call %s to %s' % (calls, func.__name__)) return func(*args, **kwargs) return wrapper if __name__ == '__main__': @tracer def spam(a, b, c): # same as: spam = tracer(spam) print(a + b + c) @tracer def eggs(x, y): # same as: eggs = tracer(eggs) print(x ** y) spam(1, 2, 3) # really calls wrapper, bound to func spam(a=4, b=5, c=6) # wrapper calls spam eggs(2, 16) # really calls wrapper, bound to eggs eggs(4, y=4) # global calls is not per-function here print('') ################################################################### # enclosing scopes, 3.0 nonlocal is per-function counter # # caveat: need to exec as string, else 'nolocal' is a # syntax error in 2.6 # import sys if sys.version_info[0] == 3: code = """ def tracer(func): # state via nested scope and nonlocal calls = 0 # instead of class attrs or global def wrapper(*args, **kwargs): # calls is per-function, not global nonlocal calls calls += 1 print('call %s to %s' % (calls, func.__name__)) return func(*args, **kwargs) return wrapper if __name__ == '__main__': @tracer def spam(a, b, c): # same as: spam = tracer(spam) print(a + b + c) @tracer def eggs(x, y): # same as: eggs = tracer(eggs) print(x ** y) spam(1, 2, 3) # really calls wrapper, bound to func spam(a=4, b=5, c=6) # wrapper calls spam eggs(2, 16) # really calls wrapper, bound to eggs eggs(4, y=4) # nonlocal calls is per-function here """ exec(code) print('')