Online Book Reader

Home Category

Learning Python - Mark Lutz [575]

By Root 1587 0
just such a combination—applying a function decorator to all the methods of a class.

Tracing with Decoration Manually

In the prior chapter we coded two function decorators, one that traced and counted all calls made to a decorated function and another that timed such calls. They took various forms there, some of which were applicable to both functions and methods and some of which were not. The following collects both decorators’ final forms into a module file for reuse and reference here:

# File mytools.py: assorted decorator tools

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

import time

def timer(label='', trace=True): # On decorator args: retain args

def onDecorator(func): # On @: retain decorated func

def onCall(*args, **kargs): # On calls: call original

start = time.clock() # State is scopes + func attr

result = func(*args, **kargs)

elapsed = time.clock() - start

onCall.alltime += elapsed

if trace:

format = '%s%s: %.5f, %.5f'

values = (label, func.__name__, elapsed, onCall.alltime)

print(format % values)

return result

onCall.alltime = 0

return onCall

return onDecorator

As we learned in the prior chapter, to use these decorators manually, we simply import them from the module and code the decoration @ syntax before each method we wish to trace or time:

from mytools import tracer

class Person:

@tracer

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]

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), remembers lastName

When this code is run, we get the following output—calls to decorated methods are routed to logic that intercepts and then delegates the call, because the original method names have been bound to the decorator:

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

Tracing with Metaclasses and Decorators

The manual decoration scheme of the prior section works, but it requires us to add decoration syntax before each method we wish to trace and to later remove that syntax when we no longer desire tracing. If we want to trace every method of a class, this can become tedious in larger programs. It would be better if we could somehow apply the tracer decorator to all of a class’s methods automatically.

With metaclasses, we can do exactly that—because they are run when a class is constructed, they are a natural place to add decoration wrappers to a class’s methods. By scanning the class’s attribute dictionary and testing for function objects there, we can automatically run methods through the decorator and rebind the original names to the results. The effect is the same as the automatic method name rebinding of decorators, but we can apply it more globally:

# Metaclass that adds tracing decorator to every method of a client class

from types import FunctionType

from mytools import tracer

class MetaTrace(type):

def __new__(meta, classname, supers, classdict):

for attr, attrval in classdict.items():

if type(attrval) is FunctionType: # Method?

classdict[attr] = tracer(attrval) # Decorate it

return type.__new__(meta, classname, supers, classdict) # Make class

class Person(metaclass=MetaTrace):

def __init__(self, name, pay):

self.name = name

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,

Return Main Page Previous Page Next Page

®Online Book Reader