Learning Python - Mark Lutz [361]
To test our Manager subclass customization, we’ve also added self-test code that makes a Manager, calls its methods, and prints it. Here’s the new version’s output:
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
Everything looks good here: bob and sue are as before, and when tom the Manager is given a 10% raise, he really gets 20% (his pay goes from $50K to $60K), because the customized giveRaise in Manager is run for him only. Also notice how printing tom as a whole at the end of the test code displays the nice format defined in Person’s __str__: Manager objects get this, lastName, and the __init__ constructor method’s code “for free” from Person, by inheritance.
Polymorphism in Action
To make this acquisition of inherited behavior even more striking, we can add the following code at the end of our file:
if __name__ == '__main__':
...
print('--All three--')
for object in (bob, sue, tom): # Process objects generically
object.giveRaise(.10) # Run this object's giveRaise
print(object) # Run the common __str__
Here’s the resulting output:
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
--All three--
[Person: Bob Smith, 0]
[Person: Sue Jones, 121000]
[Person: Tom Jones, 72000]
In the added code, object is either a Person or a Manager, and Python runs the appropriate giveRaise automatically—our original version in Person for bob and sue, and our customized version in Manager for tom. Trace the method calls yourself to see how Python selects the right giveRaise method for each object.
This is just Python’s notion of polymorphism, which we met earlier in the book, at work again—what giveRaise does depends on what you do it to. Here, it’s made all the more obvious when it selects from code we’ve written ourselves in classes. The practical effect in this code is that sue gets another 10% but tom gets another 20%, because giveRaise is dispatched based upon the object’s type. As we’ve learned, polymorphism is at the heart of Python’s flexibility. Passing any of our three objects to a function that calls a giveRaise method, for example, would have the same effect: the appropriate version would be run automatically, depending on which type of object was passed.
On the other hand, printing runs the same __str__ for all three objects, because it’s coded just once in Person. Manager both specializes and applies the code we originally wrote in Person. Although this example is small, it’s already leveraging OOP’s talent for code customization and reuse; with classes, this almost seems automatic at times.
Inherit, Customize, and Extend
In fact, classes can be even more flexible than our example implies. In general, classes can inherit, customize, or extend existing code in superclasses. For example, although we’re focused on customization here, we can also add unique methods to Manager that are not present in Person, if Managers require something completely different (Python namesake reference intended). The following snippet illustrates. Here, giveRaise redefines a superclass method to customize it, but someThingElse defines something new to extend:
class Person:
def lastName(self): ...
def giveRaise(self): ...
def __str__(self): ...
class Manager(Person): # Inherit
def giveRaise(self, ...): ... # Customize
def someThingElse(self, ...): ... # Extend
tom = Manager()
tom.lastName() # Inherited verbatim
tom.giveRaise() # Customized version
tom.someThingElse() # Extension here
print(tom) # Inherited overload method
Extra methods like this code’s someThingElse extend the existing software and are available on Manager objects only, not on Persons. For the purposes of this tutorial, however, we’ll limit our scope to customizing some of Person’s behavior by redefining it, not adding to it.
OOP: The Big Idea
As is, our code may be small, but it’s fairly functional. And really, it already illustrates the main