Learning Python - Mark Lutz [561]
[MMM]==>mapcall: 2.29260, 2.29262
[0, 2, 4, 6, 8]
allTime = 2.2926232943
giveRaise: 0.00001, 0.00001
giveRaise: 0.00001, 0.00002
55000.0 120000.0
**lastName: 0.00001, 0.00001
**lastName: 0.00001, 0.00002
Smith Jones
0.00002 0.00002
The following satisfies the second question—it’s been augmented to return the original class in optimized mode (–O), so attribute accesses don’t incur a speed hit. Really, all I did was add the debug mode test statements and indent the class further to the right. Add operator overloading method redefinitions to the wrapper class if you want to support delegation of these to the subject class in 3.0, too (2.6 routes these through __getattr__, but 3.0 and new-style classes in 2.6 do not).traceMe = False
def trace(*args):
if traceMe: print('[' + ' '.join(map(str, args)) + ']')
def accessControl(failIf):
def onDecorator(aClass):
if not __debug__:
return aClass
else:
class onInstance:
def __init__(self, *args, **kargs):
self.__wrapped = aClass(*args, **kargs)
def __getattr__(self, attr):
trace('get:', attr)
if failIf(attr):
raise TypeError('private attribute fetch: ' + attr)
else:
return getattr(self.__wrapped, attr)
def __setattr__(self, attr, value):
trace('set:', attr, value)
if attr == '_onInstance__wrapped':
self.__dict__[attr] = value
elif failIf(attr):
raise TypeError('private attribute change: ' + attr)
else:
setattr(self.__wrapped, attr, value)
return onInstance
return onDecorator
def Private(*attributes):
return accessControl(failIf=(lambda attr: attr in attributes))
def Public(*attributes):
return accessControl(failIf=(lambda attr: attr not in attributes))
# Test code: split me off to another file to reuse decorator
@Private('age') # Person = Private('age')(Person)
class Person: # Person = onInstance with state
def __init__(self, name, age):
self.name = name
self.age = age # Inside accesses run normally
X = Person('Bob', 40)
print(X.name) # Outside accesses validated
X.name = 'Sue'
print(X.name)
#print(X.age) # FAILS unles "python -O"
#X.age = 999 # ditto
#print(X.age) # ditto
@Public('name')
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
X = Person('bob', 40) # X is an onInstance
print(X.name) # onInstance embeds Person
X.name = 'Sue'
print(X.name)
#print(X.age) # FAILS unless "python –O main.py"
#X.age = 999 # ditto
#print(X.age) # ditto
Chapter 39. Metaclasses
In the prior chapter, we explored decorators and studied various examples of their use. In this final chapter of the book, we’re going continue our tool-builders focus and investigate another advanced topic: metaclasses.
In a sense, metaclasses simply extend the code-insertion model of decorators. As we learned in the prior chapter, function and class decorators allow us to intercept and augment function calls and class instance creation calls. In a similar sprit, metaclasses allow us to intercept and augment class creation—they provide an API for inserting extra logic to be run at the conclusion of a class statement, albeit in different ways than decorators. As such, they provide a general protocol for managing class objects in a program.
Like all the subjects dealt with in this part of the book, this is an advanced topic that can be investigated on an as-needed basis. In practice, metaclasses allow us to gain a high level of control over how a set of classes work. This is a powerful concept, and metaclasses are not intended for most application programmers (nor, frankly, the faint of heart!).
On the other hand, metaclasses open the door to a variety of coding patterns that may be difficult or impossible to achieve otherwise, and they are especially of interest to programmers seeking to write flexible APIs or programming tools for others to use. Even if you don’t fall into that category, metaclasses can teach you much about Python’s class model in general.
As in the prior chapter, part of our goal here is also to show more realistic code examples than we did earlier in this book. Although