Learning Python - Mark Lutz [520]
For a more realistic illustration of this phenomenon as well as its workaround, see the Private decorator example in the following chapter. There, we’ll see that it’s also possible to insert a __getattribute__ in the client class to retain its original type, although this method still won’t be called for operator overloading methods; printing still runs a __str__ defined in such a class directly, for example, instead of routing the request through __getattribute__.
As another example, the next section resurrects our class tutorial example. Now that you understand how attribute interception works, I’ll be able to explain one of its stranger bits.
* * *
Note
For an example of this 3.0 change at work in Python itself, see the discussion of the 3.0 os.popen object in Chapter 14. Because it is implemented with a wrapper that uses __getattr__ to delegate attribute fetches to an embedded object, it does not intercept the next(X) built-in iterator function in Python 3.0, which is defined to run __next__. It does, however, intercept and delegate explicit X.__next__() calls, because these are not routed through the built-in and are not inherited from a superclass like __str__ is.
This is equivalent to __call__ in our example—implicit calls for built-ins do not trigger __getattr__, but explicit calls to names not inherited from the class type do. In other words, this change impacts not only our delegators, but also those in the Python standard library! Given the scope of this change, it’s possible that this behavior may evolve in the future, so be sure to verify this issue in later releases.
* * *
Delegation-Based Managers Revisited
The object-oriented tutorial of Chapter 27 presented a Manager class that used object embedding and method delegation to customize its superclass, rather than inheritance. Here is the code again for reference, with some irrelevant testing removed:
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
class Manager:
def __init__(self, name, pay):
self.person = Person(name, 'mgr', pay) # Embed a Person object
def giveRaise(self, percent, bonus=.10):
self.person.giveRaise(percent + bonus) # Intercept and delegate
def __getattr__(self, attr):
return getattr(self.person, attr) # Delegate all other attrs
def __str__(self):
return str(self.person) # Must overload again (in 3.0)
if __name__ == '__main__':
sue = Person('Sue Jones', job='dev', pay=100000)
print(sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones', 50000) # Manager.__init__
print(tom.lastName()) # Manager.__getattr__ -> Person.lastName
tom.giveRaise(.10) # Manager.giveRaise -> Person.giveRaise
print(tom) # Manager.__str__ -> Person.__str__
Comments at the end of this file show which methods are invoked for a line’s operation. In particular, notice how lastName calls are undefined in Manager, and thus are routed into the generic __getattr__ and from there on to the embedded Person object. Here is the script’s output—Sue receives a 10% raise from Person, but Tom gets 20% because giveRaise is customized in Manager:
C:\misc> c:\python30\python person.py
Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
By contrast, though, notice what occurs when we print a Manager at the end of the script: the wrapper class’s __str__ is invoked, and it delegates to the embedded Person object’s __str__. With that in mind, watch what happens if we delete the Manager.__str__ method in this code:
# Delete the Manager __str__ method
class Manager:
def __init__(self, name, pay):
self.person = Person(name, 'mgr', pay) # Embed a Person object
def giveRaise(self,