Learning Python - Mark Lutz [552]
On the other hand, delegation wrappers could simply inherit from a common superclass that redefines operator overloading methods once, with standard delegation code. Moreover, tools such as additional class decorators or metaclasses might automate some of the work of adding such methods to delegation classes (see the class augmentation examples in Chapter 39 for details). Though still not as simple as the 2.6 solution, such techniques might help make 3.0 delegation classes more general.
Implementation alternatives: __getattribute__ inserts, call stack inspection
Although redundantly defining operator overloading methods in wrappers is probably the most straightforward workaround to Python 3.0 dilemma outlined in the prior section, it’s not necessarily the only one. We don’t have space to explore this issue much further here, so investigating other potential solutions is relegated to a suggested exercise. Because one dead-end alternative underscores class concepts well, though, it merits a brief mention.
One downside of this example is that instance objects are not truly instances of the original class—they are instances of the wrapper instead. In some programs that rely on type testing, this might matter. To support such cases, we might try to achieve similar effects by inserting a __getattribute__ method into the original class, to catch every attribute reference made on its instances. This inserted method would pass valid requests up to its superclass to avoid loops, using the techniques we studied in the prior chapter. Here is the potential change to our class decorator’s code:
# trace support as before
def accessControl(failIf):
def onDecorator(aClass):
def getattributes(self, attr):
trace('get:', attr)
if failIf(attr):
raise TypeError('private attribute fetch: ' + attr)
else:
return object.__getattribute__(self, attr)
aClass.__getattribute__ = getattributes
return aClass
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))
This alternative addresses the type-testing issue but suffers from others. For example, it handles only attribute fetches—as is, this version allows private names to be assigned freely. Intercepting assignments would still have to use __setattr__, and either an instance wrapper object or another class method insertion. Adding an instance wrapper to catch assignments would change the type again, and inserting methods fails if the original class is using a __setattr__ of its own (or a __getattribute__, for that matter!). An inserted __setattr__ would also have to allow for a __slots__ in the client class.
In addition, this scheme does not address the built-in operation attributes issue described in the prior section, since __getattribute__ is not run in these contexts, either. In our case, if Person had a __str__ it would be run by print operations, but only because it was actually present in that class. As before, the __str__ attribute would not be routed to the inserted __getattribute__ method generically—printing would bypass this method altogether and call the class’s __str__ directly.
Although this is probably better than not supporting operator overloading methods in a wrapped object at all (barring redefinition, at least), this scheme still cannot intercept and validate __X__ methods, making it impossible for any of them to be Private. Although most operator overloading methods are meant to be public, some might not be.
Much worse, because this nonwrapper approach works by adding a __getattribute__ to the decorated class, it also intercepts attribute accesses made by the class itself and validates them the same as accesses made from outside—this means the class’s method won’t be able to use Private names, either!
In fact, inserting methods this way is functionally equivalent to inheriting them, and implies