Online Book Reader

Home Category

Learning Python - Mark Lutz [536]

By Root 1649 0
it’s coded: the tracer for decorator state, and the Person for routing on to the original method. Really, self must be the tracer object, to provide access to tracer’s state information; this is true whether decorating a simple function or a method.

Unfortunately, when our decorated method name is rebound to a class instance object with a __call__, Python passes only the tracer instance to self; it doesn’t pass along the Person subject in the arguments list at all. Moreover, because the tracer knows nothing about the Person instance we are trying to process with method calls, there’s no way to create a bound method with an instance, and thus no way to correctly dispatch the call.

In fact, the prior listing winds up passing too few arguments to the decorated method, and results in an error. Add a line to the decorator’s __call__ to print all its arguments to verify this; as you can see, self is the tracer, and the Person instance is entirely absent:

<__main__.tracer object at 0x02D6AD90> (0.25,) {}

call 1 to giveRaise

Traceback (most recent call last):

File "C:/misc/tracer.py", line 56, in

bob.giveRaise(.25)

File "C:/misc/tracer.py", line 9, in __call__

return self.func(*args, **kwargs)

TypeError: giveRaise() takes exactly 2 positional arguments (1 given)

As mentioned earlier, this happens because Python passes the implied subject instance to self when a method name is bound to a simple function only; when it is an instance of a callable class, that class’s instance is passed instead. Technically, Python only makes a bound method object containing the subject instance when the method is a simple function.

Using nested functions to decorate methods

If you want your function decorators to work on both simple functions and class methods, the most straightforward solution lies in using one of the other state retention solutions described earlier—code your function decorator as nested defs, so that you don’t depend on a single self instance argument to be both the wrapper class instance and the subject class instance.

The following alternative applies this fix using Python 3.0 nonlocals. Because decorated methods are rebound to simple functions instead of instance objects, Python correctly passes the Person object as the first argument, and the decorator propagates it on in the first item of *args to the self argument of the real, decorated methods:

# A decorator for both functions and methods

def tracer(func): # Use function, not class with __call__

calls = 0 # Else "self" is decorator instance only!

def onCall(*args, **kwargs):

nonlocal calls

calls += 1

print('call %s to %s' % (calls, func.__name__))

return func(*args, **kwargs)

return onCall

# Applies to simple functions

@tracer

def spam(a, b, c): # spam = tracer(spam)

print(a + b + c) # onCall remembers spam

spam(1, 2, 3) # Runs onCall(1, 2, 3)

spam(a=4, b=5, c=6)

# Applies to class method functions too!

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) # onCall remembers giveRaise

@tracer

def lastName(self): # lastName = tracer(lastName)

return self.name.split()[-1]

print('methods...')

bob = Person('Bob Smith', 50000)

sue = Person('Sue Jones', 100000)

print(bob.name, sue.name)

sue.giveRaise(.10) # Runs onCall(sue, .10)

print(sue.pay)

print(bob.lastName(), sue.lastName()) # Runs onCall(bob), lastName in scopes

This version works the same on both functions and methods:

call 1 to spam

6

call 2 to spam

15

methods...

Bob Smith Sue Jones

call 1 to giveRaise

110000.0

call 1 to lastName

call 2 to lastName

Smith Jones

Using descriptors to decorate methods

Although the nested function solution illustrated in the prior section is the most straightforward way to support decorators that apply to both functions and class methods, other schemes are possible. The descriptor feature we explored in the prior chapter, for example, can help here as well.

Recall

Return Main Page Previous Page Next Page

®Online Book Reader