Inheritance of three object-oriented features of Python

1, Inheritance introduction

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

class ParentClass1:  # Define parent class
    pass

class ParentClass2:  # Define parent class
    pass

class SubClass1(ParentClass1):  # Single inheritance
    pass

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

Through the built-in properties of the class__ bases__ You can view all the parent classes inherited by the class:

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

In Python 2, there are classic classes and new classes. Classes that do not explicitly inherit object classes and their subclasses are classic classes. Classes that explicitly inherit object and their subclasses are new classes.

In Python 3, even if you do not explicitly inherit object, you will inherit this class by default, as follows:

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

Therefore, in Python 3, unified classes are all new classes. We will discuss the difference between classic classes and new classes later.

Tip: the object class provides the implementation of some common built-in methods, such as the built-in method used to return strings when printing objects__ str__.

2, Inheritance and abstraction

To find the inheritance relationship between classes, you need to abstract first and then inherit. Abstraction is to summarize similarities, summarize similarities between objects to obtain classes, and summarize similarities between classes to obtain parent classes, as shown in the following figure:

Based on the result of abstraction, we find the inheritance relationship:

Based on the above figure, we can see the "yes" relationship between classes (for example, humans, pigs and monkeys are animals). Subclasses can inherit / inherit all the attributes of the parent class, so inheritance can be used to solve the problem of code reuse between classes. For example, we define a Teacher class in the way we defined the Student class earlier:

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 teachers and students. Both teachers and students are human. Therefore, we can draw the following inheritance relationship 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)

There is no definition in the Teacher class__ init__ Method, but it will be found in the parent class__ init__, Therefore, it can still be instantiated normally, as follows:

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

3, Attribute lookup

With inheritance relationship, when an object looks for properties, it will first start from its own properties__ dict__ If not, find it in the class that generated the object, and then find it in 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('Bar.f1')

        
b = Bar()
b.f2()

The output result is:

Foo.f2
Bar.f1

b.f2() will find F2 in the parent class Foo, print Foo.f2 first, and then execute to self.f1(), that is, b.f1(). It will still find F1 in the class Bar in the order of object itself - > class Bar - > parent class Foo, so the print result is: Bar.f1.

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

class Foo:
    def __f1(self):  # Class definition stage has been deformed to_ Foo__f1
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.__f1()  # Class definition stage has been deformed to self_ Foo__ FA, so it will only call the methods in its own class


class Bar(Foo):
    def __f1(self):  # Class definition stage has been deformed to_ Bar__f1
        print('Bar.f1')


b = Bar()
b.f2()  # Find the f2 method in the parent class and call B_ Foo__ F1 () method, which is also found in the parent class

The output result is:

Foo.f2
Foo.f1

4, Implementation principle of inheritance

4.1 diamond problem

Most object-oriented languages do not support multiple inheritance. In Python, a subclass can inherit multiple parent classes at the same time, which can bring the advantage that a subclass can reuse multiple different parent classes, but it may also cause famous problems Diamond problem Diamond problem (or diamond problem, sometimes called "death diamond"), diamond is actually a figurative metaphor for the following inheritance structure:

Class A is at the top, class B and class C are below them respectively, and class D connects the two together at the bottom to form a diamond.

The problem caused by this inheritance structure is called diamond problem: if there is A method in A, B and C have rewritten the method, but D has not rewritten it, which version of 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 inheritance implementation principle of Python.

4.2 inheritance principle

How does Python implement inheritance?

In fact, for each class you define, Python will calculate a method resolution order (MRO) list, which is a simple linear order list of all base classes, as follows:

>>> D.mro()  """New classes are built in mro Method can view the contents of the linear list. This method is not built in the classic class"""
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

Python looks for the base class (parent class) from left to right on the MRO list until it finds the first class that matches this property.
The MRO list is constructed by a C3 linearization algorithm. Instead of going into the mathematical principle of this algorithm, it actually merges the MRO lists of all parent classes and follows the following three criteria:

  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, select the first parent class;

Therefore, the search order of obj.test() is to first find the method test from the attribute of object obj itself. If it is not found, it will be searched in turn by referring to the MRO list of class D where the initiator of attribute search (i.e. obj) is located. First, it is not found in class D, and then find the method test in B.

ps:

  1. The attribute search initiated by the object will be retrieved from the object's own attributes. If not, it will be found in the order specified by the object's class. mro();
  2. The attribute search initiated by the class will be found in 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 find the branch B first, then the branch C, and finally the branch D until we find the desired attribute:

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 methods in the above classes in turn test To verify"""

If the inheritance relationship is a diamond structure, the classical class and the new class will have different MrOS, corresponding to the two search methods of attributes: depth first and breadth first.

class G:  # In Python 2, classes and subclasses that do not inherit object are 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 in the figure above, the search order is obj - > A - > b - > e - > G - > C - > F - > D - > object
"""You can annotate the methods in the above classes in turn test To verify,Attention, please Python2.x Test in"""

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 in the figure above, the search order is obj - > A - > b - > e - > C - > F - > D - > G - > object
"""You can annotate the methods in the above classes in turn test To verify"""

4.4 Python Mixins mechanism

A subclass can inherit multiple parent classes at the same time. This design is often criticized. Firstly, it may lead to the abominable diamond problem. Secondly, 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 that based on people's world view, we can say that the car is an ("is-a") vehicle, and in people's world view, an object can't be a variety of different things. Therefore, multiple inheritance doesn't make sense in people's world view. It's just code level logic. But is it true that a class needs to inherit multiple classes?

The answer is yes. Let's take transportation as an example:

Civil aircraft, helicopters and cars are all (is-a) vehicles. The first two have a function of fly ing, but cars do not. Therefore, it is unreasonable for us to put the flight function into the parent class of vehicles as shown below:

class Vehicle:  # vehicle
    def fly(self):
        '''
        Corresponding code of flight function        
        '''
        print("I am flying")


class CivilAircraft(Vehicle):  # Civil aircraft
    pass


class Helicopter(Vehicle):  # helicopter
    pass


class Car(Vehicle):  # The car can't fly, but according to the above inheritance, the car can also fly
    pass

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

What should I do? In order to reuse the code as much as possible, we have to define an aircraft class, and then let civil aviation aircraft and helicopter inherit the two parent classes of vehicle and aircraft at the same time, so there is multiple inheritance. At this time, the inheritance must be an "is-a" relationship. How to solve this problem?

Different languages give different methods. Let's first understand the processing methods of Java: Java provides the interface function to realize multiple relay:

// Abstract base class: vehicle class
public abstract class Vehicle {
}
 
// Interface: aircraft
public interface Flyable {
    public void fly();
}
 
// Class: the class that implements the aircraft interface, in which the specific fly method is implemented, so that the following civil aircraft and helicopters can be reused directly when realizing fly
public class FlyableImpl implements Flyable {
    public void fly() {
        System.out.println("I am flying");
    }
}
 
// Civil aviation aircraft, inherited from the vehicle class, and implements the aircraft interface
public class CivilAircraft extends Vehicle implements Flyable {
    private Flyable flyable;
 
    public CivilAircraft() {
        flyable = new FlyableImpl();
    }
 
    public void fly() {
        flyable.fly();
    }
}
 
// Helicopter, inherited from the vehicle class, and implements the aircraft interface
public class Helicopter extends Vehicle implements Flyable {
    private Flyable flyable;
 
    public Helicopter() {
        flyable = new FlyableImpl();
    }
 
    public void fly() {
        flyable.fly();
    }
}
 
// Cars, inherited from vehicles,
public class Car extends Vehicle {
}

Now our aircraft has both vehicle and aircraft attributes, and we don't need to rewrite the flight method in the aircraft. At the same time, we don't break the principle of single inheritance. Aircraft is a vehicle. The ability to fly is the attribute of the aircraft, which is obtained through the inheritance interface.

Back to the topic, python language does not have interface functions, but Python provides Mixins mechanism. In short, Mixins mechanism refers to the function of subclasses mixing different classes, and these classes adopt unified naming conventions (such as Mixin suffix), so as to identify that these classes are only used to mix functions, not to identify the subordinate "is-a" relationship of subclasses, Therefore, the essence of Mixins mechanism is still multi inheritance, but it also abides by the "is-a" relationship, as follows:

class Vehicle:  # vehicle
    pass


class FlyableMixin:
    def fly(self):
        '''
        Corresponding code of flight function
        '''
        print("I am flying")


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


class Helicopter(FlyableMixin, Vehicle):  # helicopter
    pass


class Car(Vehicle):  # automobile
    pass


"""ps: It is important to adopt a specification (such as naming convention) to solve specific problems Python Usual routine"""

It can be seen that the above civil aircraft and Helicopter classes implement multiple inheritance, but the first class they inherit is named FlyableMixin instead of Flyable. This does not affect the function, but will tell later code readers that this class is a Mixin class, indicating mix in. This naming method is used to clearly tell others This class is added to the subclass as a function, not as a parent class. Its role is the same as that of the interface in Java.

Therefore, in terms of meaning, civil aircraft and Helicopter are just a Vehicle, not an aircraft.

Be very careful when using Mixin classes to implement multiple inheritance:

  • First, it must represent a function rather than an object. Python generally names mixin classes with mixin, able and able suffixes;
  • Secondly, it must have a single responsibility. If there are multiple functions, write multiple Mixin classes. A class can inherit multiple mixins. In order to ensure that the "is-a" principle of inheritance is followed, only one parent class that identifies its belonging meaning can be inherited
  • Then, it does not depend on the implementation of subclasses;
  • Finally, even if the subclass does not inherit the Mixin class, it can still work, that is, it lacks a function (for example, the aircraft can still carry passengers, but it can't fly);

Mixins is a good method to reuse code from multiple classes, but it needs to pay a corresponding price. The more Minx classes we define, the worse the code readability of subclasses will be. What's more disgusting is that when the inheritance levels become more, code readers will be confused when locating where to call a method, as follows:

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


class LoggerMixin:
    def log(self, message, filename='logfile.txt'):
        with open(filename, 'a', encoding='utf8') 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")

"""Property lookup is initiated by obj,Therefore, the class will be referenced MySubClass of MRO To retrieve properties"""
# [<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>]

The execution process is as follows:

1. First, go to the class MySubClass of object obj to find the method display. If not, go to the class LoggerMixin to find and start executing the code;

2. Execute the first line of LoggerMixin Code: execute super().display(message), refer to MySubClass.mro(), super will go to the next class, that is, class display, 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 LoggerMixin Code: self.log(message). Self is the object obj, that is, obj.log(message). The initiator of attribute search is obj, so it will search in the order of its class MySubClass.mro(), that is, MySubClass - > LoggerMixin - > display - > object (equivalent to reset). Find the method log in MySubClass and start executing super().log(message, filename='subclasslog.txt '), super will find the next class according to MySubClass.mro(), find the log method in class LoggerMixin, start execution, and finally write the log to the file subclasslog.txt;

ps: extracurricular knowledge

Java only allows multiple inheritance of interfaces. Interfaces are essentially abstract base classes with all abstract methods and no data members.
Like java, Python also has the concept of abstract class, but it also needs to be implemented with the help of modules. Abstract class is a special class. Its particularity is that it can only be inherited and cannot be instantiated. The inherited subclass must implement the methods specified by the abstract base class. In this way, it can ensure that there is always only one implementation of a specific method or attribute without ambiguity, so it can also be used Play a role in avoiding diamond problems

java interface: https://www.cnblogs.com/linhaifeng/articles/7340153.html#_label6
Python's abstract base class: https://www.cnblogs.com/linhaifeng/articles/7340153.html#_label7

5, Derivation and method reuse

Subclasses can derive their own new attributes. When searching for attributes, the attribute name in the subclass will take precedence over the parent class. For example, each Teacher also has the attribute of professional title, so we need to define its own attribute in the Teacher class__ init__ Override of parent class:

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):  # derive
        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 their own class__init__,The parent class is not called automatically"""
print(obj.name)  # lili
print(obj.sex)  # female
print(obj.age)  # 28
print(obj.title)  # Senior Lecturer

Obviously, in the subclass Teacher__ init__ The first three lines in the are writing duplicate code. There are two ways to reuse the function of the parent class in the methods derived from the child class:

Method 1: call the function of a class by name:

class Teacher(People):
    def __init__(self, name, sex, age, title):
        People.__init__(self, name, age, sex)  # You are calling 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 will be searched backward in strict accordance with the order specified in 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 Python 2 needs to be completely written as super (its own class name, self), while it can be abbreviated as super() in Python 3.

The difference between the two methods is: Method 1 has nothing to do with inheritance, while super() in method 2 depends on inheritance, and even if there is no direct inheritance relationship, super() will continue to look up later according to MRO:

# A does not inherit B
class A:
    def test(self):
        super().test()


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


class C(A, B):
    pass


C.mro()
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class 'object'>]
"""At the code level A Not at all B Subclass of, but from MRO From the list, the attributes are found in order C->A->B->object,B It's equivalent to A Parent class of"""
obj = C()
obj.test()  # Print result: from B
"""Property lookup is initiated by a class C Object of obj,Therefore, attribute searches that occur midway are all references C.mro()"""

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

You can use either of these two ways to reuse the function of the parent class in the subclass, but super() is recommended in the latest code.

6, Combination

Taking the object of another class as the data attribute in one class is called the combination of classes.

Both composition and inheritance are used to solve the problem of code reusability. The difference is: inheritance is a "yes" relationship. For example, teachers are people and students are people. When there are many similarities between classes, inheritance should be used; The combination is a "yes" relationship. For example, the teacher has a birthday and the teacher has multiple courses. When there are significant differences between classes and the smaller class is the 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


# The Teacher class reuses the code of People based on inheritance, and reuses the code of Date class and Course class based on composition
class Teacher(People):  # Teachers are people
    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.title = title
        self.courses = []  # If the teacher has a Course, you can add the object of 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, 'Doctoral supervisor', 1990, 3, 23)

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

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

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

The output result is:

<1990-3-23>
<python 3mons 3000.0>
<linux 5mons 5000.0>

At this time, the object teacher1 integrates the unique attributes of the object, the content in the Teacher class and the content in the Course class (all accessible), which is a highly integrated product.

Added by dswain on Mon, 06 Dec 2021 20:35:04 +0200