Introduction to Python Class Inheritance; Implement Python Class Inheritance and Multiple Inheritance, and Override Methods
Inherit Python Classes
Python supports class inheritance, and you can define a class that derives from a base class with the following basic form of syntax.
class <classname>(<baseclass>):
<block>
- classname part
classname
is the name of the derived class, which needs to conform to Python's identifier specification and cannot use Python keywords or reserved keywords.- baseclass part
baseclass
is an expression that returns a base class that should be accessible, similar to using other Python module attributes (e.g., variables, functions).- block part
block
is the body code of the derived class and needs to be indented with some sort of whitespace character to indicate that it belongs to the derived class.
The following class GoodMan
inherits from class Person
.
# A class on people
class Person:
title = 'How to address?'
def __init__(self, name, age):
# Define class instance variables name, age
self.name = name
self.age = age
# A class that represents a good person, inherited from Person
class GoodMan(Person):
pass
Determine Whether the Type of a Python Object is a Class or a Derived Class of It
Using the isinstance
function, you can determine whether the type of a Python object (instance) is some Python class (including abstract classes) or its derived class (subclass), or whether it is one of a set of Python classes (including abstract classes) or their derived classes (subclasses), and return True
to indicate that the object’s type is a class or its derived class, and return False
to indicate that it is not.
isinstance(object, classinfo, /)
- object parameter
The
object
parameter is the Python object whose type needs to be determined.- classinfo parameter
The
classinfo
parameter is a Python class, a tuple containing Python classes, or a union type object of Python classes (requires Python 3.10 or later), and union type (UnionType
) objects can be obtained by combining multiple Python classes with the operator|
.
# …
# Create an instance of GoodMan
goodman = GoodMan('Good man', 40)
# Determine whether the instance goodman is Person or int
print(isinstance(goodman, (Person, int)))
# Determine whether the instance goodman is one of int, float, or str
print(isinstance(goodman, int | float | str))
True
False
Determine Whether a Python Class is a Derived Class of a Class
Using the issubclass
function, you can determine whether a Python class is a derived class (subclass) of some Python class (including abstract classes), or whether it is a derived class (subclass) of some class (including abstract classes) in a set of Python classes, returning True
to indicate that the class is a derived class, and False
to indicate that it is not. It should be noted that issubclass
treats a Python class as a derived class of itself.
issubclass(class, classinfo, /)
- class parameter
The
class
parameter is the Python class that needs to be determined if it is a derived class.- classinfo parameter
The
classinfo
parameter is a Python class, a tuple containing Python classes, or a union type object of Python classes (requires Python 3.10 or later), and union type (UnionType
) objects can be obtained by combining multiple Python classes with the operator|
.
# …
# Determine if class GoodMan is a subclass of object
print(issubclass(GoodMan, object))
# Determine if class GoodMan is a subclass of str or Person
print(issubclass(GoodMan, str | Person))
# Determine if class GoodMan is a subclass of GoodMan
print(issubclass(GoodMan, GoodMan))
True
True
True
Search for Attributes in the Python's Inheritance Chain
If the attribute you want to access does not exist in the Python class that corresponds to the object, either as an instance attribute or as a class attribute, then Python searches one or more base classes in the inheritance chain until the attribute is found, or until the entire inheritance chain is searched. Failure to search for an attribute raises an exception for read operations (including method calls), and failure to search for an attribute for write operations causes a new attribute to be added to the Python object.
The order in which Python classes and their base classes are searched for in an inheritance chain needs to be determined by Python’s Method Resolution Order (MRO), and the class that actually corresponds to a Python object is usually at the top of that order.
Of course, accessing class attributes through Python classes has a similar search mechanism as accessing instance attributes or class attributes through Python objects, except that the search does not cover instance attributes of Python classes.
Unable to apply polymorphism to Python classes via variable types
Although you can further specify Python variables with :
, you can’t really declare a type for Python variables, so when accessing the attributes of a Python object through a variable, the starting point for its lookup is always the actual type of the object (instance), not the type of the variable as is the case in some languages, and this also applies to Python parameters, return values (return values can be specified using ->
).
The following class Tree
inherits from class Plant
, tree.show()
will call the show
method defined in class Tree
, which will access the instance variable name
, which can be searched for in Plant
even though it is not directly defined by Tree
, similar to accessing the instance variable variety
via Tree.variety
.
The write operation tree.age=100
will add the instance variable age
to tree
because age
did not exist before that. The read operation tree.name
will cause the exception AttributeError
because the class variable name
does not exist.
# A class on plants
class Plant:
# Class variable variety
variety = 'Unknown'
def __init__(self, name):
# Instance variable name
self.name = name
# A class that inherits from the Plant class
class Tree(Plant):
# A method to display information
def show(self):
print(self.name)
tree = Tree('Large tree')
tree.show()
print(f'Tree: What kind? {Tree.variety}')
# age does not exist, and writing it is equivalent to adding an instance variable to the tree
tree.age = 100
print(tree.age)
# ERROR The class variable name does not exist, and reading it will cause an exception
print(Tree.name)
Large tree
Tree: What kind? Unknown
100
…
AttributeError: type object 'Tree' has no attribute 'name'
Override Python Class Methods
Based on the inheritance chain search described above, you can treat all methods defined in Python classes as virtual methods, which means that they can be overridden if the access level allows it. Like Python functions defined in modules, method signatures for Python classes contain only name information by default, so methods defined in derived classes can override methods of the same name in the base class, regardless of the parameters.
In fact, overrides can target any attribute of a Python class, such as a variable or property, rather than being limited to methods.
It is not possible to directly overload methods of Python classes
As mentioned above, Python’s method signatures contain only the method name, which makes it impossible for you to overload methods of Python classes directly; if you want Python classes to have multiple methods with the same name but different parameters, you need to do it in another way, for example, through the singledispatchmethod
class of the functools
module.
The following Hero
class overrides the attack
method in the Unit
class twice, but only the second override is valid; the first override will be overwritten by the second. Attempting to call the attack
method of the Unit
class via the expression Hero().attack()
is not feasible, since only the attack
in Hero
can be located.
# A game unit
class Unit:
# The method used to launch the attack
def attack(self):
print('Unit launches an attack')
class Hero(Unit):
# This method will be overridden by the attack defined later
def attack(self, times):
while times > 0:
print('Hero launches an attack')
times -= 1
# This overrides the Unit's attack method and overrides the previously defined attack
def attack(self, times):
print(f'Hero will launch {times} attacks')
Unit().attack()
Hero().attack(5)
# ERROR Unable to call the attack method defined by the Unit class
Hero().attack()
Unit launches an attack
Hero will launch 5 attacks
…
TypeError: Hero.attack() missing 1 required positional argument: 'times'
Multiple Inheritance Python Classes
Python allows multiple inheritance of classes, and you can specify more than one base class for a Python class simply by including multiple base classes separated by ,
in the baseclass
part of the syntax for defining the class.
The problem of ambiguity in Python multiple inheritance
In object-oriented programming, the biggest problem caused by multiple inheritance is ambiguity, which is mitigated by Python’s Method Resolution Order, which attempts to linearize the search path to avoid diamond-shaped paths (the ambiguity problem). Each Python class is searched for only once, and the search results do not repeatedly contain the same attribute of the same Python class.
As for the search order of multiple Python base classes, it is determined by the order in which they are written in the baseclass
part, with the first base class having the highest priority and ranking first in the Method Resolution Order, and the subsequent base classes having a lower priority and lower ranking in the Method Resolution Order.
In the following inheritance relationship, C.name
does not create a ambiguity problem; although B1
and B2
both inherit from class A
, class A
will only be searched for once, not twice, in the Method Resolution Order, and thus the class variable name
is not ambiguous.
The statement c.show()
will call the show
method of B2
, and although B1
is written before B2
, and B1
has a higher priority, the class A
is not prioritized between B1
and B2
, but after B2
.
class A:
name = 'A'
def show(self):
print('Call the show method of A')
class B1(A):
pass
class B2(A):
def show(self):
print('Call the show method of B2')
class C(B1, B2):
pass
# There is no ambiguity problem because class A is only searched once
print(C.name)
c = C()
# This calls B2's show method instead of A's show method
c.show()
A
Call the show method of B2
Call Methods in Python Base Classes
If a Python class overrides a method in a base class and you want to continue to use the functionality implemented by the base class, you can call the base class’s method in the overridden method using the super
class, which implements a kind of proxy for accessing, whereby you can access an attribute through an instance (object) of the super
class in much the same way as an attribute can be accessed through a Python instance or class, except that the super
class needs to specify the starting point of the attribute’s search to enable access to a particular base class.
super()
super(type, object_or_type=None, /)
- type parameter
The
type
parameter is a Python class, and the first class after that Python class in the Method Resolution Order will be the starting point of the attribute search.- object_or_type parameter
The
object_or_type
parameter is the Python instance or class for which the attribute is to be searched, which should be either the Python class indicated by thetype
parameter or a derived class of it, or an instance of the Python class indicated by thetype
parameter or an instance of a derived class of it. If you ignore this parameter, the resultingsuper
object will be in an unbound state, and it may not accomplish your intended goal.
For the constructor of class super
without parameters, which can be used only in instance or class methods of Python classes, it is equivalent to writing super(type,self)
, super(type,cls)
, where type
is the current class, self
is the self
parameter of the instance method, and cls
is the cls
parameter of the class method.
For the constructor of class super
with parameters, it can be used in methods of Python classes or elsewhere, and in the case of static methods of a class, an expression of the form super(type,class)
is required, where type
is the Python class used to indicate the starting point of the search (in the Method Resolution Order, the first class after the class corresponding to type
will be used as the starting point of the search), and class
is the current class. Because it specifies the starting point for searching for a attribute, the constructor allows for more granular base class access.
Of course, not only methods in the base class, but access to other attributes such as variables and properties through the super
class is also possible.
The following code, demonstrates how to call instance methods, class methods and static methods of the base class via super
. The statement super(Dog,self).run()
causes BigDog
to bypass the run
method in the base class Dog
, and the statement super(BigDog,dog).run()
calls the run
method defined in Dog
.
class Animal:
@staticmethod
def show():
print('Here\'s Animal!')
@classmethod
def eat(cls, something):
print(f'Animal: Eat some {something}')
def run(self):
print('Animal: Let\'s run!')
class Cat(Animal):
@staticmethod
def show():
print('Cat calls Animal\'s static method show')
super(Cat, Cat).show()
@classmethod
def eat(cls, something):
print('Cat calls Animal\'s class method eat')
super().eat(something)
def run(self):
print('Cat calls Animal\'s instance method run')
# Equivalent to super(Cat, self).run()
super().run()
class Dog(Animal):
def run(self):
print('Dog: Let\'s run!')
class BigDog(Dog):
def run(self):
print('BigDog calls Animal\'s method run')
super(Dog, self).run()
Cat.show()
Cat.eat('Small dried fish')
Cat().run()
dog = BigDog()
dog.run()
# Call the run method in Dog
super(BigDog, dog).run()
Cat calls Animal's static method show
Here's Animal!
Cat calls Animal's class method eat
Animal: Eat some Small dried fish
Cat calls Animal's instance method run
Animal: Let's run!
BigDog calls Animal's method run
Animal: Let's run!
Dog: Let's run!
Python's super object may not support the [] operator
Since the super
class is only an access proxy, it may not support the way you expect to access attributes. For example, you can use the []
operator on instances of a Python class after defining the method __getitem__
, but for super
, you can call __getitem__
only with the .
operator, and using the []
operator will result in a TypeError
exception.
The following class Store
, although it defines the method __getitem__
, can only be called by super
and the .
operator.
# A class that represents a store
class Store:
def __getitem__(self, key):
print(f'Want to get items? {key}')
return key
# A class that represents the app store
class AppStore(Store):
pass
store = AppStore()
# You can use the [] operator on instances
x = store['x']
# For super, you need to use the . operator
y = super(AppStore, store).__getitem__('y')
# ERROR Using the [] operator for super will result in an exception
z = super(AppStore, store)['z']
Want to get items? x
Want to get items? y
…
TypeError: 'super' object is not subscriptable