Online Book Reader

Home Category

Learning Python - Mark Lutz [577]

By Root 1390 0
following version replaces the preceding example’s metaclass with a class decorator. It defines and uses a class decorator that applies a function decorator to all methods of a class. Although the prior sentence may sound more like a Zen statement than a technical description, this all works quite naturally—Python’s decorators support arbitrary nesting and combinations:

# Class decorator factory: apply any decorator to all methods of a class

from types import FunctionType

from mytools import tracer, timer

def decorateAll(decorator):

def DecoDecorate(aClass):

for attr, attrval in aClass.__dict__.items():

if type(attrval) is FunctionType:

setattr(aClass, attr, decorator(attrval)) # Not __dict__

return aClass

return DecoDecorate

@decorateAll(tracer) # Use a class decorator

class Person: # Applies func decorator to methods

def __init__(self, name, pay): # Person = decorateAll(..)(Person)

self.name = name # Person = DecoDecorate(Person)

self.pay = pay

def giveRaise(self, percent):

self.pay *= (1.0 + percent)

def lastName(self):

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

bob = Person('Bob Smith', 50000)

sue = Person('Sue Jones', 100000)

print(bob.name, sue.name)

sue.giveRaise(.10)

print(sue.pay)

print(bob.lastName(), sue.lastName())

When this code is run as it is, the class decorator applies the tracer function decorator to every method and produces a trace message on calls (the output is the same as that of the preceding metaclass version of this example):

call 1 to __init__

call 2 to __init__

Bob Smith Sue Jones

call 1 to giveRaise

110000.0

call 1 to lastName

call 2 to lastName

Smith Jones

Notice that the class decorator returns the original, augmented class, not a wrapper layer for it (as is common when wrapping instance objects instead). As for the metaclass version, we retain the type of the original class—an instance of Person is an instance of Person, not of some wrapper class. In fact, this class decorator deals with class creation only; instance creation calls are not intercepted at all.

This distinction can matter in programs that require type testing for instances to yield the original class, not a wrapper. When augmenting a class instead of an instance, class decorators can retain the original class type. The class’s methods are not their original functions because they are rebound to decorators, but this is less important in practice, and it’s true in the metaclass alternative as well.

Also note that, like the metaclass version, this structure cannot support function decorator arguments that differ per method, but it can handle such arguments if they apply to all methods. To use this scheme to apply the timer decorator, for example, either of the last two decoration lines in the following will suffice if coded just before our class definition—the first uses decorator argument defaults, and the second provides one explicitly:

@decorateAll(tracer) # Decorate all with tracer

@decorateAll(timer()) # Decorate all with timer, defaults

@decorateAll(timer(label='@@')) # Same but pass a decorator argument

As before, let’s use the last of these decorator lines and add the following at the end of the script to test our example with a different decorator:

# If using timer: total time per method

print('-'*40)

print('%.5f' % Person.__init__.alltime)

print('%.5f' % Person.giveRaise.alltime)

print('%.5f' % Person.lastName.alltime)

The same sort of output appears—for every method we get timing data for each and all calls, but we’ve passed a different label argument to the timer decorator:

@@__init__: 0.00001, 0.00001

@@__init__: 0.00001, 0.00002

Bob Smith Sue Jones

@@giveRaise: 0.00001, 0.00001

110000.0

@@lastName: 0.00001, 0.00001

@@lastName: 0.00001, 0.00002

Smith Jones

----------------------------------------

0.00002

0.00001

0.00002

As you can see, metaclasses and class decorators are not only often interchangeable, but also commonly complementary. Both provide advanced but powerful ways to customize and manage both class and instance objects, because both ultimately

Return Main Page Previous Page Next Page

®Online Book Reader