python inheritance rewrite single inheritance multiple inheritance polymorphic MGR lookup order MIXIN class

Class inheritance

Basic concepts

One of the three object-oriented elements, Inheritance
Both humans and cats inherit automatic objects.
Individuals inherit from their parents and inherit some of their parents' characteristics, but they can also have their own personality.
In the object-oriented world, if you inherit from the parent class, you can directly own the properties and methods of the parent class, which can reduce code redundancy and reuse. Subclasses can also define their own properties and methods.

Look at an example of not inheriting

class Animal:
    def shout(self):
print('Animal shouts') 

a = Animal()
a.shout()

class Cat:
    def shout(self):
print('Cat shouts') 

c = Cat()
c.shout()

Although the above two classes have a relationship, this relationship is not established during definition, but they are defined separately.
Animals and cats can call, but their names are different, so they are defined separately.

class Animal:
    def __init__(self, name):
				self._name = name
        
    def shout(self): # A common method is called
				print('{} shouts'.format(self._name))
        
    @property
    def name(self):
				return self._name
      
a = Animal('monster') 
a.shout()

class Cat(Animal):
    pass
  
cat = Cat('garfield')
cat.shout()
print(cat.name)

class Dog(Animal):
    pass
dog = Dog('ahuang')
dog.shout()
print(dog.name)

As can be seen from the above example, through inheritance, cat and dog classes directly inherit the properties and methods of the parent class without writing code.

inherit

class Cat(Animal) inherits from the parent class. The list of inherited classes is written in parentheses.

Inheritance allows a subclass to obtain features (properties and methods) from its parent class

Parent class

Animal is the parent class of Cat, also known as base class and superclass.

Subclass

Cat is a subclass of Animal, also known as derived class.

definition

The format is as follows

class Subclass name(Base class 1[,Base class 2,...]):
   Statement block

If there is no base class list during class definition, it is equivalent to inheriting from object. In Python 3, the object class is the root base class of all objects.

class A:
    pass
# Equivalent to
class A(object):
    pass

Note that the above example is different in Python 2.
Python supports multi inheritance, and inheritance can also be multi-level.
View inherited special properties and methods

Special properties and methods meaning
__bases__ Base class tuple of class
__base__ The first item of the base class tuple of the class
__mro__ Display method lookup order, tuple of base class
mro() method As above, return to the list
__subclasses__() List of subclasses of class
class A:
    pass
  
print(A.__base__)
print(A.__bases__)
print()
print(A.mro())
print(A.__mro__)

print(int.__subclasses__())
print(bool.mro())

Python classes in different versions

Python2.2 before, classes had no common ancestor. Later, the object class was introduced, which is the common ancestor of all classes.
For compatibility, python 2 is divided into classical class (old class) and new class.
Python 3 is full of new classes.
New classes are inherited from object, and new classes can use super.

# The following code is in Python 2 Run in X
# Classical (old style)
class A: pass

# New class
class B(object): pass

print(dir(A))
print(dir(B))
print(A.__bases__)
print(B.__bases__)

# Classical class
a = A()
print(a.__class__)
print(type(a))  # <type 'instance'>

# New class
b = B()
print(b.__class__)
print(type(b))

Access control in inheritance

class Animal:
    __a = 10
    _b = 20
    c = 30
    
    def __init__(self):
        self.__d = 40
        self._e = 50
        self.f = 60
        self.__a += 1
        
    def showa(self):
        print(self.__a)
        print(self.__class__.__a)
        
    def __showb(self):
        print(self._b)
        print(self.__a)
        print(self.__class__.__a)
        
class Cat(Animal):
    __a = 100
    _b = 200
    
c = Cat()
c.showa()
c._Animal__showb()
print(c.c)
print(c._Animal__d)
print(c._e, c.f, c._Animal__a)
print(c.__dict__)
print(c.__class__.__dict__.keys())

Inherit from the parent class. If you don't have one, you can find it in the parent class.
Private is inaccessible, but in essence, the name is still changed and placed in the class or instance where the attribute is located__ dict__ Yes. Knowing the new name, you can directly find the hidden variable. This is a dark magic skill. Use it with caution.

summary
During inheritance, public members, subclasses and instances can be accessed at will; Private members are hidden, and subclasses and instances cannot be accessed directly, but the private variable can be accessed in the method in the class where the private variable is located.
Python implements the same object-oriented inheritance mechanism as other languages through its own set of implementations.

Instance property lookup order
Instance__ dict__ → class__ dict__ → if there is inheritance → parent class__ dict__
If you don't find these places after searching, you will throw an exception. If you find them first, you will return immediately.

Method override

class Animal:
    def shout(self):
        print('Animal shouts')
        
class Cat(Animal):
    # Override parent class method
    def shout(self):
        print('miao') a = Animal()
        
a.shout()
c = Cat()
c.shout()
print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)

# Animal shouts
# miao

Can Cat override its own methods?
Can the parent class method be enhanced in Cat without complete rewriting?

class Animal:
    def shout(self):
        print('Animal shout')
        
class Cat(Animal):
    # Override parent class method
    def shout(self):
        print('miao')
    # Overrides its own method and explicitly calls the method of the parent class
    
    def shout(self):
        print(super())
        print(super(Cat, self))
        
        super().shout()
        super(Cat, self).shout() # Equivalent to super()
        self.__class__.__base__.shout(self) # Not recommended
        
a = Animal()
a.shout()
c = Cat()
c.shout()

print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)

super() can access the class properties of the parent class.
Static methods and class methods are special methods and class attributes, so the access method is the same.

Use initialization when inheriting

First look at the following code. Is there a problem

class A:
    def __init__(self, a):
        self.a = a
        
class B(A):
    def __init__(self, b, c):
        self.b = b
        self.c = c
        
    def printv(self):
        print(self.b)
        print(self.a) # Did something go wrong?
        
f = B(200, 300)
print(f.__class__.__bases__) 
f.printv()

print(f.__dict__)

According to the above example code:
If class B is declared to inherit from class A when it is defined, it is in class B__ bases__ Class A can be seen in.
But this is different from whether to call the constructor of class A.
If the method of parent class A is called in B, you can have the properties of the parent class. How to understand this sentence?
Observe example f of B__ dict__ Properties in.

class A:
    def __init__(self, a):
        self.a = a
        
class B(A):
    def __init__(self, b, c):
        A.__init__(self, b + c) # Notice who self is and what type?
        self.b = b
        self.c = c
        
    def printv(self):
        print(self.b)
        print(self.a) 
        
f = B(200, 300)     
print(f.__class__.__bases__) 
f.printv()

print(f.__dict__)

As a good habit, if the parent class is defined__ init__ Method, you should be in the subclass__ init__ Call it in.
So, when does the subclass automatically call the parent class__ init__ How?
Example 1

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'
        print('init in A')
        
class B(A):
    pass
  
b = B()
print(b.__dict__)

The initialization of B instance will automatically call the initialization of base class A__ init__ method. Note what type of instance is generated? Notice who self is.

Example 2

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'
        print('init in A')
        
class B(A):
    def __init__(self):
        self.b1 = 'b1'
        print('init in B') 
        
b = B()
print(b.__dict__)

Once the B instance is defined, the initialization__ init__ Method, the initialization of the parent class will not be called automatically__ init__ Method, which needs to be called manually.

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'
        print('init in A')
        
class B(A):
    def __init__(self):
        #super().__init__()
        #super(B, self).__init__()
        self.b1 = 'b1'
        print('init in B')
        A.__init__(self)
        
b = B()
print(b.__dict__) # Attention__ a2

summary

  • If the parent class is overridden in the child class__ init__ Method, then in the subclass__ init__ Method, the parent class should be explicitly called
    __ init__ method
  • Python is not limited to subclasses__ init__ The calling of the parent class in the method init__ The location of the method, but it should be adjusted as soon as possible
    use
  • Super () is recommended Init() or super (B, self) init()

Single inheritance

In the above example, there is only one class in the class inheritance list. This inheritance is called single inheritance.
OCP principle: use "inheritance" more and modify less. It is open to extension and closed to modification.
Purpose of inheritance: to enhance the base class and realize polymorphism on subclasses

polymorphic

In object-oriented, parent and child classes are linked by inheritance. If you can achieve different manifestations through a set of methods, it is polymorphism. The premise of polymorphism: Inheritance and coverage

Multiple inheritance

A class inherits from multiple classes is multi inheritance, which will have the characteristics of multiple classes.

Disadvantages of multiple inheritance

Multi inheritance well simulates the world, because things are rarely single inheritance, but abandoning simplicity will inevitably introduce complexity and bring conflict.
Like a child inherits characteristics from both parents. So are your eyes like mom or dad? Who should children be more like?

The implementation of multi inheritance will increase the complexity of compiler design, so some high-level programming languages abandon the multi inheritance of classes.
C + + supports multiple inheritance; Java discards multiple inheritance.

In Java, a class can implement multiple interfaces, and an interface can inherit multiple interfaces. The java interface is pure, just the declaration of methods. The inheritor must implement these methods. With these capabilities, he can do anything.

Multiple inheritance may bring ambiguity. For example, both cats and dogs inherit automatic object classes. Now if a class inherits multiple cats and dogs, both cats and dogs have shot methods. Whose shot does the subclass inherit

Solution
To realize multi inheritance language, we should solve ambiguity, depth first or breadth first.

Python multi inheritance implementation

class ClassName(Base class 1, Base class 2[, ...]):
   Class body

The left figure shows multiple inheritance (diamond inheritance), and the right figure shows single inheritance
Multiple inheritance brings the problem of path selection. Which parent class does it inherit
Python uses MRO (method resolution order) to solve the problem of base class search order.

  • For historical reasons, MRO has three search algorithms:
    • Classical algorithm, according to the definition, from left to right, depth first strategy. Before version 2.2
      The MRO in the left figure is MyClass,D,B,A,C,A
    • The new class algorithm is an upgrade of the classical algorithm, with depth first, and only the last one is reserved for repetition. Version 2.2
      The MRO in the left figure is MyClass,D,B,C,A,object
    • C3 algorithm calculates an MRO sequence table when the class is created. After 2.3, python 3 is the only algorithm supported
      The MRO in the left figure is the list of MyClass, D, B, C, a and object
      C3 algorithm solves the ambiguity of multi inheritance

The classical algorithm has A big problem. If A method in C covers the method of A, it will not access the method of C, because it will access the method of A first (depth first).
The new class algorithm still adopts depth first to solve the repetition problem, but like the classical algorithm, it does not solve the monotonicity of inheritance.
C3 algorithm solves the monotonicity of inheritance, which prevents the creation of ambiguous code in previous versions. The essence of the MRO obtained is to linearize and determine the order.
Monotonicity: suppose there are three classes A, B and C, and the mro of C is [C, A, B], then in the mro of the subclass of C, the order of A and B is monotonic.

Disadvantages of multiple inheritance

When there are many classes and inheritance is complex, there are too many inheritance paths, so it is difficult to tell what kind of inheritance path.
Python syntax allows multiple inheritance, but Python code is interpreted and executed. Errors are found only when it is executed.
In team development, if multiple inheritance is introduced, the code is likely to be uncontrollable.
Multiple inheritance should be avoided regardless of whether the programming language supports multiple inheritance.
Python's object orientation is too flexible and open, so the team should abide by the rules.

Mixin

In the implementation of many classes in Python, you can see the name of Mixin. What kind is this?
Class has the following inheritance relationships

Document class is the abstract base class of all other document classes;
Word and Pdf are subclasses of Document.
Requirement: provide printing capability for Document subclass

Idea:
1. Provide the print method in the Document
Suppose you already have the following three classes

class Document:
    def __init__(self, content):
        self.content = content
        
    def print(self): # Abstract method
        raise NotImplementedError()
        
class Word(Document): pass # Other functions are omitted
class Pdf(Document): pass # Other functions are omitted

The method provided by the base class may not be implemented specifically, because it may not be suitable for the printing of subclasses, and subclasses need to be overridden.
Methods defined but not implemented in the base class are called "abstract methods". In Python, if the abstract method defined in this way is adopted, the subclass may not implement it until the subclass uses the method.
print is a kind of capability - printing function, which is not required by all Document subclasses. From this point of view,
There is something wrong with the design of the above base class Document.
2. Add to the subclass to be printed
If you add it directly to the existing subclass Word or Pdf, although it can, it violates the principle of OCP, so you can add the printing function after inheritance. Therefore, there is the following figure

class Document: # The third-party library cannot be modified
    def __init__(self, content):
        self.content = content
class Word(Document): pass # The third-party library cannot be modified
class Pdf(Document): pass # The third-party library cannot be modified

# Single inheritance
class PrintableWord(Word):
    def print(self):
        print(self.content)
        
print(PrintableWord.__dict__)
print(PrintableWord.mro())

pw = PrintableWord('test string')
pw.print()

It seems good. If you need to provide other capabilities, how can you inherit them?
For example, if the class is used in the network, it should also have the ability of serialization, and serialization should be implemented on the class.
Serializability can also be divided into using pickle, json, messagepack, etc.
At this time, it is found that in order to add a capability, you need to add an inheritance. There may be too many classes, and the way of inheritance is not very good.
There are too many functions. Class A needs some functions and class B needs other functions. They need the free combination of multiple functions, and the inheritance and implementation is very cumbersome.

3,Mixin
Look at the code first

class Document: # The third-party library cannot be modified
    def __init__(self, content):
        self.content = content
        
class Word(Document): pass # The third-party library cannot be modified
class Pdf(Document): pass # The third-party library cannot be modified

class PrintableMixin:
    def print(self):
        print(self.content, 'Mixin')
        
class PrintableWord(PrintableMixin, Word): pass

print(PrintableWord.__dict__)
print(PrintableWord.mro())

Mixin is the mixing of other classes and brings the properties and methods of the class.

Mixin class

Mixin is essentially a multi inheritance implementation.
Mixin embodies a combined design pattern.
In object-oriented design, a complex class often needs many functions, and these functions are provided by different classes, which requires many classes to be combined together.
From the perspective of design patterns, more combinations and less inheritance.
Usage principle of Mixin class

  • Mixin class should not appear explicitly__ init__ Initialization method
  • The Mixin class only implements a single function and usually cannot work independently, because it is a partial function implementation ready to be mixed into other classes
  • A Mixin class is a class and can be inherited. Its ancestor class should also be a Mixin class

When used, Mixin class is usually in the first position of the inheritance list, such as class PrintableWord(PrintableMixin,
Word): pass
Mixin classes and decorators can enhance classes. Both of these two methods can be used according to personal preferences.
If you still need to inherit, you have to use the Mixin class.

Keywords: Python

Added by IceHawk on Thu, 27 Jan 2022 16:01:28 +0200