【Python进阶学习】多继承的使用及注意点

  • 前言
  • Python的继承
  • 单继承
  • 多继承
  • 多继承的方法执行顺序
  • 多继承时通过super方法初始化
  • 关于Python的_mro_方法
  • Python多继承的注意事项
  • 参考文献


前言

继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在运行期扩展。

有些编程语言支持多重继承,即一个子类可以同时有多个父类,比如C++编程语言;而在有些编程语言中,一个子类只能继承自一个父类,比如Java编程语言,这时可以透过实现接口来实现与多重继承相似的效果。

现今面向对象程序设计技巧中,继承并非以继承类别的“行为”为主,而是继承类别的“类型”,使得组件的类型一致。另外在设计模式中提到一个守则,“多用合成,少用继承”,此守则也是用来处理继承无法在运行期动态扩展行为的遗憾。

Python的继承

Python有单继承与多继承。单继承即子类继承于一个类,多继承即子类继承于多个类,多继承会比较少遇到,本章节主要讲单继承。

单继承

  • 在继承中基类的构造方法(init()方法)不会被自动调用,它需要在其派生类的构造方法中亲自专门调用。
  • 在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。而在类中调用普通函数时并不需要带上self参数
  • Python 总是首先查找对应类的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)
    Python单继承的案例:
class Animal(object): 
   def __init__(self, name, age):
       self.name = name
       self.age = age

   def call(self):
       print(self.name, '会叫')

class Cat(Animal):
   def __init__(self,name,age,sex):
       super(Cat, self).__init__(name,age)  # 不要忘记从Animal类执行初始化
       self.sex=sex

if __name__ == '__main__':  
   c = Cat('喵喵', 2, '男')  
   c.call()  
   
# 输出 喵喵 会叫 ,Cat继承了父类Animal的方法

多继承

对于多继承,一个子类可以用多个父类的方法,这里我们先给一个案例,再来详解如果多个父类存在同名方法、父类中又有父类的同名方法时的执行顺序
demo:

class Plant:
    def __init__(self, color):
        print("init Plant start")
        self.color = color
        print("init Plant end")

    def show(self):
        print("The Plant color is:", self.color)


class Fruit:
    def __init__(self, color):
        print("init Fruit start")
        self.color = color
        print("init Fruit end")

    def show(self):
        print("The Fruit color is:", self.color)


class Melon(Fruit):
    def __init__(self, color):
        print("init Melon start")
        super(Melon, self).__init__(color)
        self.color = color
        print("init Melon end")

    def show(self):
        print("The Melon color is:", self.color)


class Mango(Fruit, Plant):
    def __init__(self, color):
        print("init Mango start")
        Plant.__init__(self, color)
        Fruit.__init__(self, color)
        self.color = color
        print("init Mango end")

    def show_color(self):
        print("The Mango color is:", self.color)


class Watermelon(Melon, Plant):
    def __init__(self, color):
        print("init Watermelon start")
        Melon.__init__(self, color)
        Plant.__init__(self, color)
        self.color = color
        print("init Watermelon end")

    def show_color(self):
        print("The Watermelon color is:", self.color)


if __name__ == "__main__":
    mango = Mango("yellow")
    mango.show()

    watermelon = Watermelon("red")
    watermelon.show()

我们定义了植物Plant类和水果Fruit类,然后定义瓜Melon类继承水果类,定义一个芒果Mango类继承Fruit和Plant类,定义一个西瓜Watermelon类继承Melon类和Plant类。
执行结果为:

init Mango start
init Plant start
init Plant end
init Fruit start
init Fruit end
init Mango end
The Fruit color is: yellow

init Watermelon start
init Melon start
init Fruit start
init Fruit end
init Melon end
init Plant start
init Plant end
init Watermelon end
The Melon color is: red

多继承的方法执行顺序

从上面的案例我们可以看出:

  • Mango类即使初始化顺序先初始化了Plant,再初始化Fruit类,但是执行的同名方法的顺序仍然按定义该类是括号中的继承顺序;
  • Watermelon类也是按括号中的继承顺序来执行同名方法,但是执行的Melon类同名方法中,即使有该执行的Melon类的父类Fruit类也有同名方法,但还是优先执行该Melon类,这与一般的单继承规则一致。
    总结如下:
  1. 定义派生类时,需要注意圆括号中继承父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法;
  2. 如果继承的父类中,该父类还继承了别的父类,则调用同名方法时是调用最先访问到的同名方法;
  3. 支持多层父类继承,子类会继承父类所有的属性和方法,包括父类的父类的所有属性 和 方法。

多继承时通过super方法初始化

单继承中我们往往会使用super方法来初始化父类__init__方法,因为super方法可以防止同一个类被实例化多次。
但是这在多继承中往往会出现问题,我们把上面的例子改为如下demo:

class Plant(object):
    def __init__(self, color):
        print("init Plant start")
        self.color = color
        print("init Plant end")

    def show(self):
        print("The Plant color is:", self.color)


class Fruit(object):
    def __init__(self, color):
        print("init Fruit start")
        self.color = color
        print("init Fruit end")

    def show(self):
        print("The Fruit color is:", self.color)


class Melon(Fruit):
    def __init__(self, color):
        print("init Melon start")
        super(Melon, self).__init__(color)
        self.color = color
        print("init Melon end")

    def show(self):
        print("The Melon color is:", self.color)


class Mango(Fruit, Plant):
    def __init__(self, color):
        print("init Mango start")
        super(Mango, self).__init__(color)
        self.color = color
        print("init Mango end")

    def show_color(self):
        print("The Mango color is:", self.color)


class Watermelon(Melon, Plant):
    def __init__(self, color):
        print("init Watermelon start")
        super(Watermelon, self).__init__(color)
        self.color = color
        print("init Watermelon end")

    def show_color(self):
        print("The Watermelon color is:", self.color)


if __name__ == "__main__":
    mango = Mango("yellow")
    mango.show()

    watermelon = Watermelon("red")
    watermelon.show()

可以看到,我们只是把上面Mango类和Watermelon类初始化父类的方法改为了super方式,但是执行结果却如下:

init Mango start
init Fruit start
init Fruit end
init Mango end
The Fruit color is: yellow
init Watermelon start
init Melon start
init Fruit start
init Fruit end
init Melon end
init Watermelon end
The Melon color is: red

可以看到,两个实例中继承顺序排在后面的Plant类都没有被实例化,所以多继承中采用super是会有问题的,它只会实例化一次,所以super类并不适合使用在多继承场合。
其实导致这个问题的原因是Python中的一个**MRO(Method Resolution Order)**继承调用顺序的算法,我们要修复上面的问题,可以在Fruit类中加入super方法,把Fruit类改为:

class Fruit(object):
    def __init__(self, color):
        super().__init__(color)
        print("init Fruit start")
        self.color = color
        print("init Fruit end")

    def show(self):
        print("The Fruit color is:", self.color)

就可以达到实例化两个类的作用了

关于Python的_mro_方法

Python 中针对 类 提供了一个内置属性 mro 可以查看方法搜索顺序

MRO 是 method resolution order,主要用于在多继承时判断 方法、属性 的调用 路径

Python2.3以上,MRO算法就已经改为了广度优先,防止最顶级的类被多次实例化而耗费资源:

python继承与虚函数 python 继承方法_类

从C.__mro__的值可以看出, Python的方法解析优先级从高到低为:

  1. 实例本身(instance)
  2. 类(class)
  3. super class, 继承关系越近, 越先定义, 优先级越高.
    例如我们执行print(Mango.__mro__)会得到如下返回:
    (<class '__main__.Mango'>, <class '__main__.Fruit'>, <class '__main__.Plant'>, <class 'object'>)

Python多继承的注意事项

  1. 在单继承场景:super().__init__等同于类名.init
  2. 在多继承场景,super方法只能保证第一个父类被初始化,除非每一个继承的父类都实现super初始化,最终初始化了根类Object类;
    另外,子类从多个父类派生,而子类又没有自己的构造函数时:
  1. 按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数;
  2. 如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。

参考文献




  • https://zhuanlan.zhihu.com/p/30239694
  • https://lotabout.me/2020/C3-Algorithm/