Inheritance and Derivation of Python

Introduction to inheritance

Inheritance is a way to create a new class. In Python, a newly created class can inherit one or more parent classes. The newly created class can be called a subclass or a derived class, and the parent class can also be called a base class or a super class.

class ParentClass1: #Define the parent class
    pass

class ParentClass2: #Define the parent class
    pass

class SubClass1(ParentClass1): #single inheritance
    pass

class SubClass2(ParentClass1,ParentClass2): #Multiple inheritance
    pass

You can view all parent classes inherited by the class through the built-in attribute __bases__ of the class.

>>>SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

In Python2, there are classic classes and new-style classes. Classes that do not explicitly inherit the object class and their subclasses are all classic classes. Classes that explicitly inherit object and their subclasses are all is a new-style class. In Python3, even if the object is not explicitly inherited, the class will be inherited by default, as follows

>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

Therefore, all are new-style classes in Python3

Tip: The object class provides the implementation of some commonly used built-in methods, such as the built-in method __str__ used to return a string when printing an object

Two Inheritance and Abstraction

To find out the inheritance relationship between classes, you need to abstract first and then inherit. Abstraction means summarizing the similarities, summarizing the similarities between objects to get classes, and summarizing the similarities between classes to get parent classes, as shown in the figure below

Based on the abstract results, we found the inheritance relationship

Based on the above figure, it can be seen that the inheritance between classes refers to what is the relationship (for example, humans, pigs, and monkeys are all animals). Subclasses can inherit/inherit all the properties of the parent class, so inheritance can be used to solve the problem of code reusability between classes. For example, define a Teacher class in the same way that the Student class is defined

class Teacher:
    school='Tsinghua University'
    
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age
    
    def teach(self):
        print('%s is teaching' %self.name)

There are duplicate codes between the class Teacher and Student. Both teachers and students are human beings, so the following inheritance relationship can be obtained to realize code reuse

class People:
    school='Tsinghua University'
    
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age
    
class Student(People):
    def choose(self):
        print('%s is choosing a course' %self.name)

class Teacher(People):
    def teach(self):
        print('%s is teaching' %self.name)

The __init__ method is not defined in the Teacher class, but __init__ will be found from the parent class, so it can still be instantiated normally, as follows

>>> teacher1=Teacher('lili','male',18)
>>> teacher1.school,teacher1.name,teacher1.sex,teacher1.age
('Tsinghua University', 'lili', 'male', 18)

Use of super

#A does not inherit from B, but the super in A will continue to look for it based on C.mro()
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print('from B')
class C(A,B):
    pass

c=C()
c.test() #Print result: from B

Summary: super is similar to a nested design. When the code is executed to super instantiation, first find the parent class at the same level. If there are no other parent classes, execute its own parent class, and then go down.

Three attribute search

With the inheritance relationship, when an object searches for an attribute, it first looks for it from the object’s own __dict__, if not, it goes to the subclass, and then goes to the parent class…

>>> class Foo:
... def f1(self):
... print('Foo. f1')
... def f2(self):
... print('Foo. f2')
... self. f1()
...
>>> class Bar(Foo):
... def f1(self):
... print('Foo. f1')
...
>>> b=Bar()
>>> b.f2()
Foo.f2
Foo.f1

b.f2() will find f2 in the parent class Foo, first print Foo.f2, and then execute to self.f1(), that is, b.f1(), still follow: object itself -> class Bar -> parent class The order of Foo is searched in turn, and f1 is found in the class Bar, so the printed result is Foo.f1

If the parent class does not want the subclass to override its own method, it can set the method as private by starting with a double underscore

>>> class Foo:
... def __f1(self): # transformed into _Foo__fa
... print('Foo. f1')
... def f2(self):
... print('Foo. f2')
... self.__f1() # transforms into self._Foo__fa, so it will only call the method in the class it is in
...
>>> class Bar(Foo):
... def __f1(self): # transformed into _Bar__f1
... print('Foo. f1')
...
>>>
>>> b=Bar()
>>> b.f2() #Find the f2 method in the parent class, and then call the b._Foo__f1() method, which is also found in the parent class
Foo.f2
Foo.f1

Four inheritance principles

4.1 Diamond problem

Most object-oriented languages do not support multiple inheritance, but in Python, a subclass can inherit multiple parent classes at the same time, which can certainly bring the benefit of a subclass being able to reuse multiple different parent classes, but It may also lead to the famous Diamond problem (or diamond problem, sometimes called “death diamond”). The rhombus is actually a metaphor for the following inheritance structure

Class A is at the top, Classes B and C are each below it, and Class D is at the bottom connecting the two together to form a diamond.

The problem caused by this inheritance structure is called the diamond problem: If there is a method in A, B and/or C both override the method, but D does not override it, which version of the method does D inherit: B’s or C’s? As follows

class A(object):
    def test(self):
        print('from A')


class B(A):
    def test(self):
        print('from B')


class C(A):
    def test(self):
        print('from C')


class D(B,C):
    pass


obj = D()
obj.test() # The result is: from B

To understand how obj.test() finds the method test, you need to understand the principle of python’s inheritance implementation

4.2 Inheritance principle

How does python implement inheritance? For each class you define, Python calculates a method resolution order (MRO) list, which is simply a linearly ordered list of all base classes, as follows

>>> D.mro() # The new class has a built-in mro method to view the content of the linear list, and the classic class does not have this built-in method
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

Python will start looking for base classes from left to right on the MRO list until it finds the first class that matches this attribute.
And the construction of this MRO list is realized by a C3 linearization algorithm. Let’s not delve into the mathematical principles of this algorithm. It actually merges the MRO lists of all parent classes and follows the following three guidelines:

1. The subclass will be checked before the parent class
2. Multiple parent classes are checked according to their order in the list
3. If there are two legal choices for the next class, choose the first parent class

Therefore, the search order of obj.test() is to first search for the method test from the properties of the object obj itself. Not found in class D, then find method test in B

1. The attribute search initiated by the object will be retrieved from the object’s own attributes, otherwise it will be searched in order according to the order specified by the object’s class.mro(),
2. The attribute search initiated by the class will be searched in order according to the order specified by the current class.mro(),

4.3 Depth-first and breadth-first

Referring to the following code, the multi-inheritance structure is a non-diamond structure. At this time, we will first find the B branch, then find the C branch, and finally find the D branch until we find the attribute we want.

class E:
    def test(self):
        print('from E')


class F:
    def test(self):
        print('from F')


class B(E):
    def test(self):
        print('from B')


class C(F):
    def test(self):
        print('from C')


class D:
    def test(self):
        print('from D')


class A(B, C, D):
    # def test(self):
    # print('from A')
    pass


print(A. mro())
'''
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__ .D'>, <class 'object'>]
'''

obj = A()
obj.test() # The result is: from B
# You can annotate the method test in the above class in turn for verification

If the inheritance relationship is a diamond structure, then the classic class and the new class will have different MROs, corresponding to two search methods for attributes: depth-first and breadth-first

class G: # In python2, classes that do not inherit object and their subclasses are all classic classes
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    # print('from A')
    pass

obj = A()
obj.test() # As shown above, the search order is: obj->A->B->E->G->C->F->D->object
# You can annotate the method test in the above class in turn for verification, please note that please test in python2.x

class G(object):
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    # print('from A')
    pass

obj = A()
obj.test() # As shown above, the search order is: obj->A->B->E->C->F->D->G->object
# You can annotate the method test in the above class in turn for verification

4.4 Python Mixins mechanism

A subclass can inherit multiple parent classes at the same time. This design is often criticized. First, it may lead to a nasty diamond problem. Second, inheritance should be an “is-a” relationship in people’s world view. For example, the reason why the car class can inherit the vehicle class is because based on the human world view, we can say: a car is a (“is-a”) vehicle, and in the human world view, an item cannot be a variety of different objects. Therefore, multiple inheritance does not make sense in the human worldview, it is just logic at the code level. But is there such a situation, does a class really need to inherit multiple classes?

The answer is yes, let’s take transportation as an example:

Civil aviation planes, helicopters, and cars are all one (is-a) means of transportation. The former two have a function of flying, but cars do not. So we put the flight function into the parent class of means of transportation as shown below is unreasonable

class Vehicle: # Vehicle
    def fly(self):
        '''
        Corresponding codes for flight functions
        '''
        print("I am flying")


class CivilAircraft(Vehicle): # Civil Aircraft
    pass


class Helicopter(Vehicle): # Helicopter
    pass


class Car(Vehicle): # Cars can't fly, but according to the above inheritance relationship, cars can fly too
    pass

However, if civil aviation aircraft and helicopters each write their own flight methods, it violates the principle of code reuse as much as possible (if there are more and more flying tools in the future, there will be more and more repeated codes).

what to do? ? ? In order to reuse the code as much as possible, it is necessary to define an aircraft class, and then let the civil aviation aircraft and the helicopter inherit the two parent classes of the vehicle and the aircraft at the same time, so that multiple inheritance appears. At this time, it violates the inheritance must be “is-a” relationship. How to solve this problem?

Python provides the Mixins mechanism. Simply put, the Mixins mechanism refers to the subclass mixing (mixin) functions of different classes, and these classes adopt a unified naming convention (such as the Mixin suffix) to identify these classes only for mixing functions. , is not used to identify the subordinate “is-a” relationship of subclasses, so the essence of the Mixins mechanism is still multiple inheritance, but it also abides by the “is-a” relationship, as follows

class Vehicle: # Vehicle
    pass


class FlyableMixin:
    def fly(self):
        '''
        Corresponding codes for flight functions
        '''
        print("I am flying")


class CivilAircraft(FlyableMixin, Vehicle): # Civil Aircraft
    pass


class Helicopter(FlyableMixin, Vehicle): # Helicopter
    pass


class Car(Vehicle): # car
    pass

# ps: It is python's customary routine to adopt certain norms (such as naming conventions) to solve specific problems

It can be seen that the CivilAircraft and Helicopter classes above implement multiple inheritance, but we named the first class it inherits FlyableMixin instead of Flyable. This does not affect the function, but it will tell people who read the code later that this class It is a Mixin class, which means mix-in. This naming method is used to clearly tell others (the idiomatic method of python language). This class is added to the subclass as a function, not as a parent class. It The role of the interface in Java. So in terms of meaning, the CivilAircraft and Helicopter classes are just a Vehicle, not an aircraft.

Be very careful about implementing multiple inheritance with Mixin classes

  • First of all, it must represent a certain function, not an item. Python generally uses Mixin, able, and ible as suffixes for the naming of mixin classes
  • Secondly, it must have a single responsibility. If there are multiple functions, then write multiple Mixin classes. A class can inherit multiple Mixins. In order to ensure that the “is-a” of inheritance is followed In principle, only one parent class that identifies its attribution meaning can be inherited
  • Then, it does not depend on the implementation of subclasses
  • Finally, even if the subclass does not inherit this Mixin class, it can still work, but a certain function is missing. (For example, planes can still carry passengers, but they cannot fly)

Mixins are a great way to reuse code from multiple classes, but there is a price to pay. The more Minx classes we define, the less readable the subclass code will be, and worse, at the inheritance level When there are more, code readers will be confused when locating where a certain method is called, as follows

class Displayer:
    def display(self, message):
        print(message)


class LoggerMixin:
    def log(self, message, filename='logfile.txt'):
        with open(filename, 'a') as fh:
            fh. write(message)

    def display(self, message):
        super().display(message) # Please refer to the next section for the usage of super
        self. log(message)


class MySubClass(LoggerMixin, Displayer):
    def log(self, message):
        super().log(message, filename='subclasslog.txt')


obj = MySubClass()
obj.display("This string will be shown and logged in subclasslog.txt")


# The initiator of the attribute search is obj, so the attribute will be retrieved by referring to the MRO of the class MySubClass
#[<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>]

# 1. First, go to the class MySubClass of the object obj to find the method display, if not, go to the class LoggerMixin to find it, find it and start executing the code
# 2. Execute the first line of code of LoggerMixin: Execute super().display(message), refer to MySubClass.mro(), super will go to the next class, that is, the class Displayer, find display, start executing the code, and print the message" This string will be shown and logged in subclasslog.txt"
# 3. Execute the second line of code of LoggerMixin: self.log(message), self is the object obj, that is, obj.log(message), the initiator of attribute search is obj, so it will follow its class MySubClass.mro(), That is, search in the order of MySubClass->LoggerMixin->Displayer->object, find the method log in MySubClass, start executing super().log(message, filename='subclasslog.txt'), super will search according to MySubClass.mro() In the next class, find the log method in the class LoggerMixin to start executing, and finally write the log to the file subclasslog.txt

Five Derivation and Method Reuse

Subclasses can derive their own new attributes. When searching for attributes, the attribute names in the subclass will be searched prior to the parent class. For example, each teacher also has the attribute of professional title. We need to define this in the Teacher class The class’s own __init__ overrides the parent class’s

>>> class People:
... school='Tsinghua University'
...
... def __init__(self,name,sex,age):
... self.name=name
... self.sex=sex
... self. age = age
...
>>> class Teacher(People):
... def __init__(self,name,sex,age,title): # derived
... self.name=name
... self.sex=sex
... self. age = age
... self.title=title
... def teach(self):
... print('%s is teaching' %self.name)
...
>>> obj=Teacher('lili','female',28,'senior lecturer') # will only find the __init__ in its own class, and will not automatically call the parent class
>>> obj.name, obj.sex, obj.age, obj.title
('lili', 'female', 28, 'Senior Lecturer')

Obviously, the first three lines in __init__ in the subclass Teacher are writing repetitive code. If you want to reuse the functions of the parent class in the method derived from the subclass, there are two ways to achieve it.

Method 1: Call a function of a certain class “by name”

>>> class Teacher(People):
... def __init__(self,name,sex,age,title):
... People.__init__(self,name,age,sex) #The call is a function, so you need to pass in self
... self.title=title
... def teach(self):
... print('%s is teaching' %self.name)
...

Method 2: super()

Calling super() will get a special object, which is specially used to refer to the properties of the parent class, and it will be searched backwards in strict accordance with the order specified by MRO

>>> class Teacher(People):
... def __init__(self,name,sex,age,title):
... super().__init__(name,age,sex) #The binding method is called, and self is automatically passed in
... self.title=title
... def teach(self):
... print('%s is teaching' %self.name)
...

Tip: The use of super in Python2 needs to be completely written as super (own class name, self), while it can be abbreviated as super() in python3.

The difference between these two methods is: the first method has nothing to do with inheritance, while the super() of the second method is dependent on inheritance, and even if there is no direct inheritance relationship, super() will still continue to look up according to MRO

>>> #A does not inherit from B
...class A:
... def test(self):
... super().test()
...
>>> class B:
... def test(self):
...print('from B')
...
>>> class C(A,B):
... pass
...
>>> C.mro() # At the code level, A is not a subclass of B, but from the MRO list, when searching for attributes, it is in the order C->A->B->object, and B is equivalent to A The "parent class" of
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class 'object'>]
>>> obj=C()
>>> obj.test() # The initiator of the attribute search is the object obj of class C, so the attribute search that occurs halfway refers to C.mro()
from B

obj.test() first finds the test method under A, executes super().test() will continue to search () based on the current position of the MRO list (based on C.mro()), and then in B Find the test method and execute it.

Regarding the two ways of reusing the functions of the parent class in the subclass, you can use either one, but it is recommended to use super() in the latest code

Six combinations

Using objects of another class as data attributes in one class is called a combination of classes. Both composition and inheritance are used to solve the problem of code reusability. The difference is: Inheritance is a “is” relationship. For example, the teacher is a person and the student is a person. When there are many similarities between classes, inheritance should be used; while composition is a “has” relationship. For example, the teacher has a birthday, and the teacher has multiple courses. When there are significant differences between the classes, and the smaller class is a component required by the larger class, the combination should be used, as shown in the following example

class Course:
    def __init__(self, name, period, price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))

class Date:
    def __init__(self, year, mon, day):
        self.year=year
        self.mon=mon
        self.day=day
    def tell_birth(self):
       print('<%s-%s-%s>' %(self.year,self.mon,self.day))

class People:
    school='Tsinghua University'
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

#Teacher class reuses People's code based on inheritance, and reuses Date class and Course class code based on combination
class Teacher(People): #The teacher is a person
    def __init__(self,name,sex,age,title,year,mon,day):
        super().__init__(name,age,sex)
        self.birth=Date(year,mon,day) #The teacher has a birthday
        self.courses=[] #The teacher has courses, you can add objects of the Course class to the list after instantiation
    def teach(self):
        print('%s is teaching' %self.name)


python=Course('python','3mons',3000.0)
linux=Course('linux','5mons',5000.0)
teacher1=Teacher('lili','female',28,'PhD supervisor',1990,3,23)

# teacher1 has two courses
teacher1.courses.append(python)
teacher1.courses.append(linux)

# Reuse functionality of the Date class
teacher1. birth. tell_birth()

# Reuse the functionality of the Course class
for obj in teacher1.courses:
    obj. tell_info()

At this time, the object teacher1 integrates the unique attributes of the object, the contents of the Teacher class, and the contents of the Course class (all of which can be accessed), and is a highly integrated product.