Online Book Reader

Home Category

Learning Python - Mark Lutz [542]

By Root 1908 0
one specific method.

Class decorators provide an alternative and convenient way to code this __getattr__ technique to wrap an entire interface. In 2.6 and 3.0, for example, the prior class example can be coded as a class decorator that triggers wrapped instance creation, instead of passing a pre-made instance into the wrapper’s constructor (also augmented here to support keyword arguments with **kargs and to count the number of accesses made):

def Tracer(aClass): # On @ decorator

class Wrapper:

def __init__(self, *args, **kargs): # On instance creation

self.fetches = 0

self.wrapped = aClass(*args, **kargs) # Use enclosing scope name

def __getattr__(self, attrname):

print('Trace: ' + attrname) # Catches all but own attrs

self.fetches += 1

return getattr(self.wrapped, attrname) # Delegate to wrapped obj

return Wrapper

@Tracer

class Spam: # Spam = Tracer(Spam)

def display(self): # Spam is rebound to Wrapper

print('Spam!' * 8)

@Tracer

class Person: # Person = Tracer(Person)

def __init__(self, name, hours, rate): # Wrapper remembers Person

self.name = name

self.hours = hours

self.rate = rate

def pay(self): # Accesses outside class traced

return self.hours * self.rate # In-method accesses not traced

food = Spam() # Triggers Wrapper()

food.display() # Triggers __getattr__

print([food.fetches])

bob = Person('Bob', 40, 50) # bob is really a Wrapper

print(bob.name) # Wrapper embeds a Person

print(bob.pay())

print('')

sue = Person('Sue', rate=100, hours=60) # sue is a different Wrapper

print(sue.name) # with a different Person

print(sue.pay())

print(bob.name) # bob has different state

print(bob.pay())

print([bob.fetches, sue.fetches]) # Wrapper attrs not traced

It’s important to note that this is very different from the tracer decorator we met earlier. In Coding Function Decorators, we looked at decorators that enabled us to trace and time calls to a given function or method. In contrast, by intercepting instance creation calls, the class decorator here allows us to trace an entire object interface—i.e., accesses to any of its attributes.

The following is the output produced by this code under both 2.6 and 3.0: attribute fetches on instances of both the Spam and Person classes invoke the __getattr__ logic in the Wrapper class, because food and bob are really instances of Wrapper, thanks to the decorator’s redirection of instance creation calls:

Trace: display

Spam!Spam!Spam!Spam!Spam!Spam!Spam!Spam!

[1]

Trace: name

Bob

Trace: pay

2000

Trace: name

Sue

Trace: pay

6000

Trace: name

Bob

Trace: pay

2000

[4, 2]

Notice that the preceding code decorates a user-defined class. Just like in the original example in Chapter 30, we can also use the decorator to wrap up a built-in type such as a list, as long as we either subclass to allow decoration syntax or perform the decoration manually—decorator syntax requires a class statement for the @ line.

In the following, x is really a Wrapper again due to the indirection of decoration (I moved the decorator class to module file tracer.py in order to reuse it this way):

>>> from tracer import Tracer # Decorator moved to a module file

>>> @Tracer

... class MyList(list): pass # MyList = Tracer(MyList)

>>> x = MyList([1, 2, 3]) # Triggers Wrapper()

>>> x.append(4) # Triggers __getattr__, append

Trace: append

>>> x.wrapped

[1, 2, 3, 4]

>>> WrapList = Tracer(list) # Or perform decoration manually

>>> x = WrapList([4, 5, 6]) # Else subclass statement required

>>> x.append(7)

Trace: append

>>> x.wrapped

[4, 5, 6, 7]

The decorator approach allows us to move instance creation into the decorator itself, instead of requiring a premade object to be passed in. Although this seems like a minor difference, it lets us retain normal instance creation syntax and realize all the benefits of decorators in general. Rather than requiring all instance creation calls to route objects through a wrapper manually, we need only augment classes with decorator syntax:

@Tracer # Decorator approach

class Person: ...

bob = Person('Bob',

Return Main Page Previous Page Next Page

®Online Book Reader