Inheritance & Polymorphism
Inheritance & Polymorphism
- Specify single inheritance by putting a base class in parentheses after defining a class’s name
- Subclasses have all of the methods of their base class
- It’s often best to explicitly call a base class initializer from a subclass’s initializer
- If a class with a single base class doesn’t define an initializer, the base class’s initializer will be called automatically on construction
- Python treats
__init__()like any other method - Base class
__init__()is not called if overridden - Use
super()to call base class__init__() isinstance()takes an object as its first argument and a type as its secondisinstance()determines if its first argument is an instance of the second argument, or any subclass of the second argumentisinstance()can accept a tuple of types as its second argument, in which it returns True if the first argument is of any of those types- Checking for specific types is rare in Python and is sometimes regarded as bad design
isinstance()determines if its first argument is a direct or indirect subclass of, or the same type as, the second argument- Multiple inheritance means having more than one direct base class
- You declare multiple base classes with a comma-separated list of class names in parentheses after a class’s name in a class definition
- A class can have as many base classes as you want
- Python uses a well-defined “method resolution order” to resolve methods at runtime
- If a multiply-inheriting class defines no initializer, Python will automatically call the initializer of its first base class on construction
__bases__is a tuple of types on a class object which defines the base classes for the class__bases__is in the same order as in the class definition__bases__is populated for both single and multiple inheritance- Method resolution order defines the order in which Python will search an inheritance graph for methods
- MRO is short for Method Resolution Order
- MRO is stored as a tuple of types in the
__mro__attribute of a class - The
mro()method on type objects returns the contents of__mro__as a list - To resolve a method, Python uses the first entry in a class’s MRO which has the requested method
- MRO is dependent on base class declaration order
- MRO is calculated by Python using the C3 algorithm
- MRO honors base-class ordering from class definitions
- MRO puts subclasses before base classes
- The relative order of classes in an MRO is consistent across all classes
- It is possible to specify an inconsistent base class ordering, in which case Python will raise a TypeError when the class definition is reached
super()operates by using the elements in an MRO that come after some specified typesuper()returns a proxy object which forwards calls to the correct objects- There are two distinct types of
super()proxies, bound and unbound - Unbound
super()proxies are primarily used for implementing other Python features - Bound proxies can be bound to either class objects or instances
- Calling
super()with a base-class and derived-class argument returns a proxy bound to a class - Calling
super()with a class and an instance of that class returns a proxy bound to an instance - A
super()proxy takes the MRO of its second argument (or the type of its second argument), finds the first argument in that MRO, and uses everything after it in the MRO for method resolution - Since class-bound proxies aren’t bound to an instance, you can’t directly call instance methods that they resolve for you
- However, classmethods resolved by class-bound proxies can be called directly
- Python will raise a TypeError if the second argument is not a subclass or instance of the first argument
- Inappropriate use of
super()can violate some design constraints * Callingsuper()with no arguments inside an instance method produces an instance-bound proxy - Calling
super()with no arguments inside a classmethod produces a class-bound proxy - In both cases, the no-argument form of
super()is the same as callingsuper()with the method’s class as the first argument and the method’s first argument as the second - Since
super()works on MROs and not just a class’s base classes, class can be designed to cooperate without prior knowledge of one another - The class object is at the core of Python’s object model
- object is the ultimate base class for all other classes in Python
- If you don’t specify a base class for a class, Python automatically uses object as the base
- Because object is in every class’s inheritance graph, it shows up in every MRO.
- object provides hooks for Python’s comparison operators
- object provides default
__repr__()and__str__()implementations - object implements the core attribute lookup and management functionality in Python
- Inheritance in Python is best used as a way to share implementation
Explanation with example
Example code
The code below demonstrates the weird
super()in Pythonfrom pprint import pprint as pp class Parent(object): name = 'Parent' @classmethod def do_otherthing(self): print('This is from Parent. {} do_otherthing'.format(self.name)) ## print('This is from Parent. do_otherthing {}'.format(self.name)) def do_something(self): print('This is from Parent. The name is {}'.format(self.name)) class Child(Parent): name = 'Child' @classmethod def do_otherthing(self): print('This is from Child. {} do_otherthing'.format(self.name)) ## print('This is from Child. do_otherthing {}'.format(self.name)) def do_something(self): print("This is from Child. The name is {} ".format(self.name)) class OtherChild(Parent): name = 'OtherChild' def do_something(self): print("This is from OtherChild. The name is {} ".format(self.name)) class OtherOtherChild(Parent): name = 'OtherOtherChild' def do_something(self): print("This is from OtherOtherChild. The name is {} ".format(self.name)) class GrandChild(Child, OtherChild, OtherOtherChild): name = 'GrandChild' @classmethod def do_otherthing(self): print('This is from GrandChild. {} do_otherthing'.format(self.name)) def do_something(self): print("This is from GrandChild. The name is {}".format(self.name)) if __name__ == "__main__": pp(Child.__mro__) pp(GrandChild.__mro__) c = Child() c.do_something() gc = GrandChild() gc.do_something() print('Class bound super()') super(Child, Child).do_otherthing() super(Child, GrandChild).do_otherthing() super(GrandChild, GrandChild).do_otherthing() print('Instance bound super()') super(Child, gc).do_something() super(OtherChild, gc).do_something() super(GrandChild , gc).do_something() ### test result #(<class '__main__.Child'>, #<class '__main__.Parent'>, #<class 'object'>) #(<class '__main__.GrandChild'>, ## <class '__main__.Child'>, ## <class '__main__.OtherChild'>, ## <class '__main__.OtherOtherChild'>, ## <class '__main__.Parent'>, ## <class 'object'>) ## This is from Child. The name is Child ## This is from GrandChild. The name is GrandChild ## Class bound super() ## This is from Parent. Child do_otherthing ## This is from Parent. GrandChild do_otherthing ## This is from Child. GrandChild do_otherthing ## Instance bound super() ## This is from OtherChild. The name is GrandChild ## This is from OtherOtherChild. The name is GrandChild ## This is from Child. The name is GrandChild ## Multi-inheritance in Python is very different from ## other OO language like C++, C#, Java ## Above result make me surprised a the first time, ## but after I check the MRO then I can understand ## why many veterans suggest to avoid multi-inheritance. ## Its `super` "Magic" really confuses many people.
Explanation with break down
- Let’s break down the how the super works with sample code
Case 1: Parent and child
Case 1:
- code :
super(Child, Child).do_otherthing() supertakes the MRO of its second argumentChild<class '__main__.Child'>, <class '__main__.Parent'>, <class 'object'>superfinds the first argumentChildin that MRO, and uses everything after it in the MRO for method resolutionsuperuses the method fromParentwhich is afterChild, and bind theChildclass object
- code :
Case 2: Grandparent and grandchild
Case 2:
- code :
super(GrandChild, GrandChild).do_otherthing() supertakes the MRO of its second argumentGrandChild<class '__main__.GrandChild'>, <class '__main__.Child'>, <class '__main__.OtherChild'>, <class '__main__.OtherOtherChild'>, <class '__main__.Parent'>, <class 'object'>superfinds the first argumentGrandChildin that MRO, and uses everything after it in the MRO for method resolutionsuperuses the method fromChildwhich is afterGrandChild, and bind theGrandChildclass object
- code :
Case 3: Grandparent, children & grandchild
Case 3:
- code :
super(OtherChild, gc).do_something() supertakes the MRO of the typeGrandChildof its second argumentgc<class '__main__.GrandChild'>, <class '__main__.Child'>, <class '__main__.OtherChild'>, <class '__main__.OtherOtherChild'>, <class '__main__.Parent'>, <class 'object'>superfinds the first argumentOtherChildin that MRO, and uses everything after it in the MRO for method resolutionsuperuses the method fromOtherOtherChildwhich is afterOtherChild, and bind thegcclass object
- code :