Online Book Reader

Home Category

Learning Python - Mark Lutz [535]

By Root 1705 0
you may still be able to avoid globals and classes by making use of function attributes for some changeable state instead. In recent Pythons, we can assign arbitrary attributes to functions to attach them, with func.attr=value. In our example, we can simply use wrapper.calls for state. The following works the same as the preceding nonlocal version because the counter is again per-decorated-function, but it also runs in Python 2.6:

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

Return Main Page Previous Page Next Page

®Online Book Reader