Online Book Reader

Home Category

Learning Python - Mark Lutz [534]

By Root 1862 0
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)

Like the original, this uses class instance attributes to save state explicitly. Both the wrapped function and the calls counter are per-instance information—each decoration gets its own copy. When run as a script under either 2.6 or 3.0, the output of this version is as follows; notice how the spam and eggs functions each have their own calls counter, because each decoration creates a new class instance:

call 1 to spam

6

call 2 to spam

15

call 1 to eggs

65536

call 2 to eggs

256

While useful for decorating functions, this coding scheme has issues when applied to methods (more on this later).

Enclosing scopes and globals

Enclosing def scope references and nested defs can often achieve the same effect, especially for static data like the decorated original function. In this example, though, we would also need a counter in the enclosing scope that changes on each call, and that’s not possible in Python 2.6. In 2.6, we can either use classes and attributes, as we did earlier, or move the state variable out to the global scope, with global declarations:

calls = 0

def tracer(func): # State via enclosing 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

@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!

Unfortunately, moving the counter out to the common global scope to allow it to be changed like this also means that it will be shared by every wrapped function. Unlike class instance attributes, global counters are cross-program, not per-function—the counter is incremented for any traced function call. You can tell the difference if you compare this version’s output with the prior version’s—the single, shared global call counter is incorrectly updated by calls to every decorated function:

call 1 to spam

6

call 2 to spam

15

call 3 to eggs

65536

call 4 to eggs

256

Enclosing scopes and nonlocals

Shared global state may be what we want in some cases. If we really want a per-function counter, though, we can either use classes as before, or make use of the new nonlocal statement in Python 3.0, described in Chapter 17. Because this new statement allows enclosing function scope variables to be changed, they can serve as per-decoration and changeable data:

def tracer(func): # State via enclosing 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

@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_ not per-function here

Now, because enclosing scope variables are not cross-program globals, each wrapped function gets its own counter again, just as for classes and attributes. Here’s the new output when run under 3.0:

call 1 to spam

6

call 2 to spam

15

call 1 to eggs

65536

call 2 to eggs

256

Function attributes

Finally, if you are not using Python 3.X and don’t have a nonlocal statement,

Return Main Page Previous Page Next Page

®Online Book Reader