Online Book Reader

Home Category

Learning Python - Mark Lutz [537]

By Root 1900 0
from our discussion in that chapter that a descriptor may be a class attribute assigned to objects with a __get__ method run automatically when that attribute is referenced and fetched (object derivation is required in Python 2.6, but not 3.0):

class Descriptor(object):

def __get__(self, instance, owner): ...

class Subject:

attr = Descriptor()

X = Subject()

X.attr # Roughly runs Descriptor.__get__(Subject.attr, X, Subject)

Descriptors may also have __set__ and __del__ access methods, but we don’t need them here. Now, because the descriptor’s __get__ method receives both the descriptor class and subject class instances when invoked, it’s well suited to decorating methods when we need both the decorator’s state and the original class instance for dispatching calls. Consider the following alternative tracing decorator, which is also a descriptor:

class tracer(object):

def __init__(self, func): # On @ decorator

self.calls = 0 # Save func for later call

self.func = func

def __call__(self, *args, **kwargs): # On call to original func

self.calls += 1

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

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

def __get__(self, instance, owner): # On method attribute fetch

return wrapper(self, instance)

class wrapper:

def __init__(self, desc, subj): # Save both instances

self.desc = desc # Route calls back to decr

self.subj = subj

def __call__(self, *args, **kwargs):

return self.desc(self.subj, *args, **kwargs) # Runs tracer.__call__

@tracer

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

...same as prior... # Uses __call__ only

class Person:

@tracer

def giveRaise(self, percent): # giveRaise = tracer(giverRaise)

...same as prior... # Makes giveRaise a descriptor

This works the same as the preceding nested function coding. Decorated functions invoke only its __call__, while decorated methods invoke its __get__ first to resolve the method name fetch (on instance.method); the object returned by __get__ retains the subject class instance and is then invoked to complete the call expression, thereby triggering __call__ (on (args...)). For example, the test code’s call to:

sue.giveRaise(.10) # Runs __get__ then __call__

run’s tracer.__get__ first, because the giveRaise attribute in the Person class has been rebound to a descriptor by the function decorator. The call expression then triggers the __call__ method of the returned wrapper object, which in turn invokes tracer.__call__.

The wrapper object retains both descriptor and subject instances, so it can route control back to the original decorator/descriptor class instance. In effect, the wrapper object saves the subject class instance available during method attribute fetch and adds it to the later call’s arguments list, which is passed to __call__. Routing the call back to the descriptor class instance this way is required in this application so that all calls to a wrapped method use the same calls counter state information in the descriptor instance object.

Alternatively, we could use a nested function and enclosing scope references to achieve the same effect—the following version works the same as the preceding one, by swapping a class and object attributes for a nested function and scope references, but it requires noticeably less code:

class tracer(object):

def __init__(self, func): # On @ decorator

self.calls = 0 # Save func for later call

self.func = func

def __call__(self, *args, **kwargs): # On call to original func

self.calls += 1

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

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

def __get__(self, instance, owner): # On method fetch

def wrapper(*args, **kwargs): # Retain both inst

return self(instance, *args, **kwargs) # Runs __call__

return wrapper

Add print statements to these alternatives’ methods to trace the two-step get/call process on your own, and run them with the same test code as in the nested function alternative shown earlier. In either coding, this descriptor-based scheme is also substantially subtler than the nested function option,

Return Main Page Previous Page Next Page

®Online Book Reader