Richard, Fantastic and very helpful slice. Thank you. A question: In the sample, discussing the fact that the new class must call the initializer in the parent class, as in: Account.__init__(self, customerName, accountID, startBalance) You said: "Note that the call to __init__ in the parent class is done as an unbound class method call." I don't understand the significance of that aside. The term "unbound method call" seems to escape me. Can you clarify? Thanks. From: pythonvis-bounce@xxxxxxxxxxxxx [mailto:pythonvis-bounce@xxxxxxxxxxxxx] On Behalf Of Richard Dinger Sent: Sunday, February 15, 2015 10:50 AM To: pythonvis Subject: [pythonvis] Slice of Py: Class Inheritance The previous Slice article introduced classes as a first look at object oriented programming in Python and showed how to build simple classes that encapsulate some state and behavior into an object. In this Slice we will take a look at how to extend a class using what is referred to as inheritance. What is Inheritance? For this discussion, inheritance is the software mechanism of building a class by including the data and methods of an existing class into a new class. The new class may then use some of the inherited code unchanged, modify or enhance some of it or even hide some of the code. In addition the new class normally adds additional code to extend the features of the class beyond those of the inherited class. Inheritance is, therefore, a form of software reuse. But what exactly does it mean to include the data and methods of an existing class in a new class? Well, for example, lets say you want to build a deck of cards class . Since a deck of cards is a sort of ordered sequence, you decide to base your deck on the list class by having Deck inherit and extend list, so Deck is really a list of cards. If you write the following: >>>class Deck(list): ... pass You now have a class called Deck that essentially is a clone of list. But, unlike the list class, you can modify Deck to include methods like shuffle or deal. Depending on the client code that will use your new Deck class, you may also wish to hide some of the list methods like sort or reverse so they cannot be accidentally called. The Deck is said to have an is-a relationship with list. That is a Deck is a kind of list. Deck extends the list type to a more specific kind of type. Conversely list is a more abstract type of class. Inheritance vs Composition As a quick aside, inheritance is not the only way to extend classes and build systems. You could also build a Deck class using composition wherein the Deck class contains a list instance. The list is then an internal container for the Deck. In this situation the design is a has-a relationship since the Deck has a list as an attribute. The Deck class design in this case eliminates client code from mistakenly accessing methods like sort or reverse in the Deck class. Which approach you use will depend on the problem that you are trying to solve and how you perceive that problem. In practice, though, most classes use a combination of both approaches. Class Diagrams and Nomenclature Classes are organized by their inheritance patterns. Unfortunately these patterns are often displayed as a diagram of class boxes connected by relationship lines or arrows. So imagine the following simple inheritance diagram usually called a tree or a class tree. The tree has a box at the top called Shape. Below and to the left is a box labeled Triangle, which is connected to the Shape box by an arrow. Below and to the right is a box labeled Circle, which is also connected to the Shape box by an arrow. This diagram is just an organization chart for a family of classes. The boxes and arrows explain the inheritance of the classes. Each of the two lower boxes satisfy the 'is-a' relationship, that is a Circle is a Shape, but a Shape is not necessarily a Circle. A Shape may be either a circle or a triangle. Oddly enough, the top of a tree diagram is called its root and the top class in a class tree is often called the base class. A class immediately above another is called its parent and the class immediately below is its child class. Any class above is an ancestor while any below is a descendent class. The term super class is often used for ancestor and sub class for descendent. Multiple Inheritance One more complication, Python supports the notion of multiple inheritance. This simply means that a class can inherit from more than one super class. For example, a car class in an accounting system might inherit from the Asset class as well as from the Vehicle class. In this case a Car is an Asset and it is also a Vehicle. Since multiple inheritance adds complexity to your software, you should probably avoid it until you have a good understanding of Python. But if the problem fits a multiple inheritance design, there is nothing wrong with using the tool. The Account Class Revisited Recall in the previous Slice that a simple account class was developed for a debit card system. Now suppose that a new executive account type is being requested by bank management. Certain preferred customers should be able to make purchases even though their account balance is insufficient as long as a predefined overdraft limit is not exceeded. Each preferred account would have a unique limit established when the account is set up. Once overdrawn, additional purchase transactions are denied until a positive balance is restored, so only one overdraft at a time. A fee would, of course, be assessed for this service. In a traditional procedural programming environment, this would mean tearing up the code to rework it in several places. In the transaction processing code, for example, this type of problem is usually solved by conditional statements like: if customer is preferred: doPreferredProcessing() else: doStandardProcessing() The above idiom will appear everywhere account specific processing is performed such as where customers are created, where transactions are processed and where reports are printed. In an object oriented approach, that construct is usually needed less often, but I will explain that after we finish this new class. Starting the Executive Account Class After importing the original Account class in the file bank.py that we developed in the previous Slice article, we start the executive account class definition with a class statement just like any other class. # begin code indent 2 spaces from bank import Account class ExecutiveAccount(Account): " The ExecutiveAccount class" # Overdraft fee charged by the bank overFee = 30.0 # suspend code The class name is in Camel case (first letter of each word a capital) and the parent class follows enclosed in parenthesis. The ExecutiveAccount 'inherits' from the Account class. In order to ensure that you are using the so called new classes, I recommend that you have all your classes inherit from either object or a class that inherits from object. We have something different in this class a class variable called overFee, which is the fee the bank charges for overdrawing the account. This class variable contains data that is shared by every instance of the class so it belongs here at the class level rather than being duplicated in every instance. Class variables can be initialized in the class definition or through code that has access to the class namespace. Initializing the ExecutiveAccount Class # resume code def __init__(self, customerName, accountID, startBalance, overLimit): " Initialize an ExecutiveAccount class object." # First, initialize the base Account class Account.__init__(self, customerName, accountID, startBalance) # and initialize overLimit not in base class self.overLimit = overLimit # suspend code A sub class almost always requires its own __init__ method to handle additional input parameters. The new ExecutiveAccount will need an additional attribute for holding the overdrawLimit in addition to the normal Account initializing data. Python does not automatically call the ancestor initialization methods, so that must be done explicitly in the sub class initialization method as shown. Note that the call to __init__ in the parent class is done as an unbound class method call. In addition to calling the parent initialization, any initializing required for the sub class is provided as well. Revising The show and str Methods # resume code def show(self): " Show current status of an ExecutiveAccount object." # if overdrawn, prepend amount over if self.balance < 0.0: print '*Overdrawn %.2f Limit %.2f*' % (-self.balance, self.overLimit), # then call parent class show method Account.show(self) def __str__(self): " Automatic string conversion method." # append limit to parent class details return '%s oLimit %.2f' % (Account.__str__(self), self.overLimit) # suspend code These display output methods are revised by using the inherited methods and extending them with the additional data contained in the new class. As in the initialization method, ancestor methods are called as unbound class methods. Revising the Processing Methods Here is the good news: most of the processing does not change from what is already in the Account class. The deposit and report methods in the Account class can handle the new ExecutiveAccount class, so only the purchase method must be revised for the new overdraft limit and fee. # resume code def purchase(self, purchaseID, vender, amount): " Process a valid purchase transaction or reject if not within bounds." # if amount less than balance, do normal processing if amount <= self.balance: Account.purchase(self, purchaseID, vender, amount) # purchase transaction fails if already overdrawn elif self.balance < 0.0: print '* Refused Already Overdrawn vender %s $%8.2f balance $%8.2f' % (vender, amount, self.balance) return # purchase transaction fail if current balance + overdraft limit exceeded elif amount > (self.balance + self.overLimit): print '* Refused Exceeds Overdraft Limit vender %s $%8.2f balance $%8.2f' % (vender, amount, self.balance) return # if balance exceeded, but not overLimit, do new processing elif amount > self.balance and amount <= (self.balance + self.overLimit): self.balance -= (amount + ExecutiveAccount.overFee) # make a transaction record record = 'Purchase ID %s Vender %s sd$%8.2f *balance $%8.2f*' % (purchaseID, vender, amount, self.balance) self.transactions.append(record) return # end code Now the sub class purchase method checks if the purchase amount is within the current account balance and if so, the sub class purchase method simply delegates the work to the parent class by calling the purchase method in the Account class as an unbound class method. And if the purchase amount exceeds the current balance, the special processing is done here in the ExecutiveAccount purchase method. Notice that the class variable overFee must b qualified with the class name. That completes the code for the ExecutiveAccount class. The complete code including some simple testing is also at the end of this Slice. How Classes Avoid Type Testing As promised earlier, now we can look at how object oriented design can simplify code in a system like this banking system where there are different types of accounts. For example, in a transaction processing kind of procedure you might have something like the following: # get account ID and purchase amount from transactions for ID, amount in transactionList: # lookup account type and current balance type, balance = lookupAccount(ID) # process account according to type if type is 'normal': doNormalProcessing(amount, balance) elif type is 'executive': doExecutive(amount, balance) and so on ... The above sort of pattern appears everywhere some form of type dependent processing takes place. Now consider our simple little system of accounts. We simply have accounts and each type of account 'knows' how to do its own processing. The processing process outlined above using the account objects looks like this: # get account ID and purchase amount from transactions for ID, amount in transactionList: # lookup account object account = lookupAccount(ID) # process account account.doProcessing(amount) and so on ... Now additional account types can always be included in the system with little impact on existing code. And the tedious task of searching through the code trying to find all those type dependent sections is no longer required. Method Resolution Order How classes and objects work in Python is largely determined by how the following expression is evaluated: object.attribute We have been using this sort of expression from our earliest programming attempts to access both data attributes and function attributes within objects. If object is not a user defined class type object, the attribute is fetched directly from the object. If object is a user defined class type, however, the search for attribute starts with object and then the class tree is searched starting with object's class proceeding from bottom to top and from left to right returning the first occurrence of attribute found. This search is called the Method Resolution Order (MRO), although it applies to data attributes as well as methods. Python classes are essentially trees of namespaces that are searched during program execution to fetch attributes. Since the first occurrence found of an attribute is returned from the search, attributes located lower down in the tree 'hide' attributes with the same name located higher in the tree. When you use a unbound class method call, you force the method in the ancestor class to be called giving you the ability to override the MRO when needed. Sub Classes Override Super Classes This is really the heart of object oriented programming and inheritance. As we saw earlier with Deck inheriting from list, when creating a new class that inherits from a super class, the new class will use attributes from the super class unless they are redefined in the descendent. So a sub class extends the capabilities of its ancestor classes. Now two major features of classes and the object oriented story have been introduced; encapsulation and inheritance. With these tools alone many problems can be solved. But classes and objects have more features to learn, like operator overloading, that will be discussed in future slices. The file bank2.py with the code for the ExecutiveAccount class follows. # bank2.py bank classes 2 spaces each indent # begin code indent 2 spaces from bank import Account class ExecutiveAccount(Account): " The ExecutiveAccount class" # Overdraft fee charged by the bank overFee = 30.0 # suspend code # resume code def __init__(self, customerName, accountID, startBalance, overLimit): " Initialize an ExecutiveAccount class object." # First, initialize the base Account class Account.__init__(self, customerName, accountID, startBalance) # and initialize overLimit not in base class self.overLimit = overLimit # suspend code # resume code def show(self): " Show current status of an ExecutiveAccount object." # if overdrawn, prepend amount over if self.balance < 0.0: print '*Overdrawn %.2f Limit %.2f*' % (-self.balance, self.overLimit), # then call parent class show method Account.show(self) def __str__(self): " Automatic string conversion method." # append limit to parent class details return '%s oLimit %.2f' % (Account.__str__(self), self.overLimit) # suspend code # resume code def purchase(self, purchaseID, vender, amount): " Process a valid purchase transaction or reject if not within bounds." # if amount less than balance, do normal processing if amount <= self.balance: Account.purchase(self, purchaseID, vender, amount) # purchase transaction fails if already overdrawn elif self.balance < 0.0: print '* Refused Already Overdrawn vender %s $%8.2f balance $%8.2f' % (vender, amount, self.balance) return # purchase transaction fail if current balance + overdraft limit exceeded elif amount > (self.balance + self.overLimit): print '* Refused Exceeds Overdraft Limit vender %s $%8.2f balance $%8.2f' % (vender, amount, self.balance) return # if balance exceeded, but not overLimit, do new processing elif amount > self.balance and amount <= (self.balance + self.overLimit): self.balance -= (amount + ExecutiveAccount.overFee) # make a transaction record record = 'Purchase ID %s Vender %s sd$%8.2f *balance $%8.2f*' % (purchaseID, vender, amount, self.balance) self.transactions.append(record) return # end code # Testing if __name__ == '__main__': a = ExecutiveAccount('Smith, John', 'A1234', 200, 100) a.show() a.deposit('D12345', 100.00) a.show() a.purchase('C0001', 'Supermarket', 100.00) a.purchase('C0002', 'Drug Store', 100.00) a.show() a.purchase('C0003', 'Computer Shop', 180.00) a.show() a.purchase('C0002', 'Drug Store', 1.00) a.report() print '__str__ function output is:\n', a