Learning Python - Mark Lutz [535]
def tracer(func): # State via enclosing scope and func attr
def wrapper(*args, **kwargs): # calls is per-function, not global
wrapper.calls += 1
print('call %s to %s' % (wrapper.calls, func.__name__))
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
Notice that this only works because the name wrapper is retained in the enclosing tracer function’s scope. When we later increment wrapper.calls, we are not changing the name wrapper itself, so no nonlocal declaration is required.
This scheme was almost relegated to a footnote, because it is more obscure than nonlocal in 3.0 and is probably better saved for cases where other schemes don’t help. However, we will employ it in an answer to one of the end-of-chapter questions, where we’ll need to access the saved state from outside the decorator’s code; nonlocals can only be seen inside the nested function itself, but function attributes have wider visibility.
Because decorators often imply multiple levels of callables, you can combine functions with enclosing scopes and classes with attributes to achieve a variety of coding structures. As we’ll see later, though, this sometimes may be subtler than you expect—each decorated function should have its own state, and each decorated class may require state both for itself and for each generated instance.
In fact, as the next section will explain, if we want to apply function decorators to class methods, too, we also have to be careful about the distinction Python makes between decorators coded as callable class instance objects and decorators coded as functions.
Class Blunders I: Decorating Class Methods
When I wrote the first tracer function decorator above, I naively assumed that it could also be applied to any method—decorated methods should work the same, but the automatic self instance argument would simply be included at the front of *args. Unfortunately, I was wrong: when applied to a class’s method, the first version of the tracer fails, because self is the instance of the decorator class and the instance of the decorated subject class in not included in *args. This is true in both Python 3.0 and 2.6.
I introduced this phenomenon earlier in this chapter, but now we can see it in the context of realistic working code. Given the class-based tracing decorator:
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)
decoration of simple functions works as advertised earlier:
@tracer
def spam(a, b, c): # spam = tracer(spam)
print(a + b + c) # Triggers tracer.__init__
spam(1, 2, 3) # Runs tracer.__call__
spam(a=4, b=5, c=6) # spam is an instance attribute
However, decoration of class methods fails (more lucid readers might recognize this as our Person class resurrected from the object-oriented tutorial in Chapter 27):
class Person:
def __init__(self, name, pay):
self.name = name
self.pay = pay
@tracer
def giveRaise(self, percent): # giveRaise = tracer(giverRaise)
self.pay *= (1.0 + percent)
@tracer
def lastName(self): # lastName = tracer(lastName)
return self.name.split()[-1]
bob = Person('Bob Smith', 50000) # tracer remembers method funcs
bob.giveRaise(.25) # Runs tracer.__call__(???, .25)
print(bob.lastName()) # Runs tracer.__call__(???)
The root of the problem here is in the self argument of the tracer class’s __call__ method—is it a tracer instance or a Person instance? We really need both as