Saturday 16 April 2016

Classes 5: Inheritance

The key defining feature of any object orientated programming language is the concept of inheritance. Just as you and I can inherit property, cash or genetic traits from our parents, grandparents and other ancestors. Python objects like classes can inherit attributes, properties and methods from their ancestors. Which also includes any built in object.

The following examples will show how the basic mechanics of inheritance currently work in Python 3.x.x. The first example simply shows a child class inheriting a method from a parent class. The critical lesson to learn here is the manner in which we tell the Python interpreter that we want the child class to inherit this method in the first place.

Note that the syntax, the manner in which we write the code, is exactly the same as when we define a parameter Python should expect to be passed to a function or method. So there is nothing especially new or difficult to get to grips with here. When it comes time to call the inherited method however. We don't reference the parent. We only ever reference the child as though the method was defined as part of the child class from the get go.

#!/usr/bin/env python3

__project__= "Python Classes: Inheritance"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 1 $"
__date__ = "$Date: 2016/04/14 22:06:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Define the parent class. It contains 1 method.
class parentClass():
        def parentMessage(self,text):
                print(text)
                
# Define the child class. It contains no methods, attributes or properties.
class childClass(parentClass):
        pass

# Main Program and Global Variables.
child = childClass()
child.parentMessage("Text is printing from parent.")

Most people however have 2 parents. And in Python our child class can also have 2 parents. In fact it can have a lot more than 2. The second example shows this concept of inheritance from multiple parent classes.

#!/usr/bin/env python3

__project__= "Python Classes: Inheritance"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 2 $"
__date__ = "$Date: 2016/04/14 22:06:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Define the parent classes. They contain 1 method each.
class fatherClass():
        def fatherMessage(self,text):
                print(text)

class motherClass():
        def motherMessage(self,text):
                print(text)
                
# Define the child class. It contains no methods, attributes or properties.
class childClass(fatherClass,motherClass):
        pass

# Main Program and Global Variables.
child = childClass()
child.fatherMessage("Text is printing from father.")
child.motherMessage("Text is printing from mother.")

Just as in real life. This ancestral linage of our child class does not stop with it's parent classes. If the father class inherits a method, attribute or property from a grandfather class. Then so too will the child class. As is shown in example 3.

#!/usr/bin/env python3

__project__= "Python Classes: Inheritance"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 3 $"
__date__ = "$Date: 2016/04/14 22:06:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Define the ancestor classes. They contain 1 method each.
class grandfatherClass():
        def grandfatherMessage(self):
                print("This text is printing from grandfatherMessage().")

class fatherClass(grandfatherClass):
        def fatherMessage(self):
                print("This text is printing from fatherMessage().")
                
# Define the child class. It contains no methods, attributes or properties.
class childClass(fatherClass):
        pass

# Main Program and Global Variables.
grandfather = grandfatherClass()
father = fatherClass()
child = childClass()

print("\ngrandfather:")
grandfather.grandfatherMessage()

print("\nfather:")
father.grandfatherMessage()
father.fatherMessage()

print("\nchild:")
child.grandfatherMessage()
child.fatherMessage()

There are however occasions when inherited features need to be overridden. Creating a new class based on an existing class can save us a lot of time and code. This makes our program more efficient since we get to reuse code in much the same way we can reuse functions. However we might need to override an inherited method because it doesn't do exactly what we need it to do.

Python allows us to do this simply by redefining the method at the point in the ancestral lineage where we wish the new version of the method to take effect. In the fourth example below. We override the method we inherited from the grandfatherClass within the childClass.

By doing this we don't need to redefine the entire grandfatherClass. Which means the fatherClass and any objects based on it alone will be unaffected as will any objects based on the grandfatherClass.

#!/usr/bin/env python3

__project__= "Python Classes: Inheritance"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 4 $"
__date__ = "$Date: 2016/04/14 22:06:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Define the ancestor classes. They contain 1 method each.
class grandfatherClass():
        def grandfatherMessage(self):
                print("This text is printing from grandfatherMessage().")

class fatherClass(grandfatherClass):
        def fatherMessage(self):
                print("This text is printing from fatherMessage().")
                
# Define the child class. It has 1 method to override grandfatherMessage(self).
class childClass(fatherClass):
        def grandfatherMessage(self):
                print("The child has overridden the grandfather!!!")

# Main Program and Global Variables.
grandfather = grandfatherClass()
father = fatherClass()
child = childClass()

print("\ngrandfather:")
grandfather.grandfatherMessage()

print("\nfather:")
father.grandfatherMessage()
father.fatherMessage()

print("\nchild:")
child.grandfatherMessage()
child.fatherMessage()

Inheritance and the ability to override things presents us with a problem however. There is nothing stopping us from defining attributes, properties or methods with the same name in each of the classes we create. This means the names of inherited class members can come into conflict. Many developers assign common names to common functions. But the code within will all be written slightly differently to produce different outcomes. To make sure our programs behave as we expect them to, Python has a set of rules. These rules dictate how Python searches through the ancestral lineage to find the inherited class member.

It should become clear from the next two examples that Python will search top to bottom and left to right. In other words Python will search through the entire lineage of the left-most parent class stated in our child class definition before moving onto the next parent.

In the case of our example that means searching the lineage of fatherClass for the parentMessage() method before moving onto motherClass. And since it will find this method in the grandfatherClass, it will use this version of the method.

Another interesting point to note here is that grandfatherClass is an ancestor of fatherClass. But not motherClass.

#!/usr/bin/env python3

__project__= "Python Classes: Inheritance"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 5 $"
__date__ = "$Date: 2016/04/14 22:06:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Define the ancestor classes. They contain 1 method each.
class grandfatherClass():
        def grandfatherMessage(self):
                print("This text is printing from grandfatherMessage().")

class fatherClass(grandfatherClass):
        def parentMessage(self):
                print("From fatherClass():")
                print("This text is printing from parentMessage().")

class motherClass():
        def parentMessage(self):
                print("From motherClass():")
                print("This text is printing from parentMessage().")
                                
# Define the child classes. They contain no methods, attributes or properties.
class child1Class(fatherClass,motherClass):
        pass

class child2Class(motherClass,fatherClass):
        pass

# Main Program and Global Variables.
grandfather = grandfatherClass()
father = fatherClass()
mother = motherClass()
child1 = child1Class()
child2 = child2Class()

print("\ngrandfather:")
grandfather.grandfatherMessage()

print("\nfather:")
father.grandfatherMessage()
father.parentMessage()

print("\nmother:")
mother.parentMessage()

print("\nchild1:")
child1.grandfatherMessage()
child1.parentMessage()

print("\nchild2:")
child2.grandfatherMessage()
child2.parentMessage()

If we switch the order fatherClass and motherClass are passed to childClass. We can see that Python will now search for the parentMessage() method in motherClass first. And therefore use the motherClass version of that method.

#!/usr/bin/env python3

__project__= "Python Classes: Inheritance"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 6 $"
__date__ = "$Date: 2016/04/14 22:06:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Define the ancestor classes. They contain 1 method each. Except for
# fatherClass(). It inherits everything from grandfatherClass().
class grandfatherClass():
        def grandfatherMessage(self):
                print("This text is printing from grandfatherMessage().")

        def parentMessage(self):
                print("From grandfatherClass():")
                print("This text is printing from parentMessage().")

class fatherClass(grandfatherClass):
        pass
        
class motherClass():
        def parentMessage(self):
                print("From motherClass():")
                print("This text is printing from parentMessage().")
                                
# Define the child classes. They contain no methods, attributes or properties.
class child1Class(fatherClass,motherClass):
        pass

class child2Class(motherClass,fatherClass):
        pass

# Main Program and Global Variables.
grandfather = grandfatherClass()
father = fatherClass()
mother = motherClass()
child1 = child1Class()
child2 = child2Class()

print("\ngrandfather:")
grandfather.grandfatherMessage()
grandfather.parentMessage()

print("\nfather:")
father.grandfatherMessage()
father.parentMessage()

print("\nmother:")
mother.parentMessage()

print("\nchild1:")
child1.grandfatherMessage()
child1.parentMessage()

print("\nchild2:")
child2.grandfatherMessage()
child2.parentMessage()

No comments:

Post a Comment