Learning Python - Mark Lutz [374]
Test Your Knowledge: Quiz
When we fetch a Manager object from the shelve and print it, where does the display format logic come from?
When we fetch a Person object from a shelve without importing its module, how does the object know that it has a giveRaise method that we can call?
Why is it so important to move processing into methods, instead of hardcoding it outside the class?
Why is it better to customize by subclassing rather than copying the original and modifying?
Why is it better to call back to a superclass method to run default actions, instead of copying and modifying its code in a subclass?
Why is it better to use tools like __dict__ that allow objects to be processed generically than to write more custom code for each type of class?
In general terms, when might you choose to use object embedding and composition instead of inheritance?
How might you modify the classes in this chapter to implement a personal contacts database in Python?
Test Your Knowledge: Answers
In the final version of our classes, Manager ultimately inherits its __str__ printing method from AttrDisplay in the separate classtools module. Manager doesn’t have one itself, so the inheritance search climbs to its Person superclass; because there is no __str__ there either, the search climbs higher and finds it in AttrDisplay. The class names listed in parentheses in a class statement’s header line provide the links to higher superclasses.
Shelves (really, the pickle module they use) automatically relink an instance to the class it was created from when that instance is later loaded back into memory. Python reimports the class from its module internally, creates an instance with its stored attributes, and sets the instance’s __class__ link to point to its original class. This way, loaded instances automatically obtain all their original methods (like lastName, giveRaise, and __str__), even if we have not imported the instance’s class into our scope.
It’s important to move processing into methods so that there is only one copy to change in the future, and so that the methods can be run on any instance. This is Python’s notion of encapsulation—wrapping up logic behind interfaces, to better support future code maintenance. If you don’t do so, you create code redundancy that can multiply your work effort as the code evolves in the future.
Customizing with subclasses reduces development effort. In OOP, we code by customizing what has already been done, rather than copying or changing existing code. This is the real “big idea” in OOP—because we can easily extend our prior work by coding new subclasses, we can leverage what we’ve already done. This is much better than either starting from scratch each time, or introducing multiple redundant copies of code that may all have to be updated in the future.
Copying and modifying code doubles your potential work effort in the future, regardless of the context. If a subclass needs to perform default actions coded in a superclass method, it’s much better to call back to the original through the superclass’s name than to copy its code. This also holds true for superclass constructors. Again, copying code creates redundancy, which is a major issue as code evolves.
Generic tools can avoid hardcoded solutions that must be kept in sync with the rest of the class as it evolves over time. A generic __str__ print method, for example, need not be updated each time a new attribute is added to instances in an __init__ constructor. In addition, a generic print method inherited by all classes only appears, and need only be modified, in one place—changes in the generic version are picked up by all classes that inherit from the generic class. Again, eliminating code redundancy cuts future development effort; that’s one of the primary assets classes bring to the table.
Inheritance is best at coding extensions based on direct customization