多重继承和super()简介

本文概述

  • 继承快速概述
  • super介绍
  • super继承和多重继承
  • 我们学到了什么
继承概述 随着Python项目和程序包的发展, 你不可避免地要利用类并在执行此操作时应用DRY(不要重复自己)原理。类继承是一种基于其他类创建类以保持DRY的绝妙方法。这篇文章将涵盖更高级的继承概念, 而基本继承将不再深入讨论。我们将快速介绍一下, 但是那里有更好, 更详细的介绍。这里有一些入门资源:Python课程中的面向对象编程和Python面向对象编程(OOP):教程。
那么什么是类继承?与遗传学类似, 子类可以从父类” 继承” 属性和方法。让我们跳入一些代码作为示例。在下面的代码块中, 我们将通过从父类继承的子类演示继承。
输入
class Parent: def __init__(self): self.parent_attribute = 'I am a parent'def parent_method(self): print('Back in my day...')# Create a child class that inherits from Parent class Child(Parent): def __init__(self): Parent.__init__(self) self.child_attribute = 'I am a child'# Create instance of child child = Child()# Show attributes and methods of child class print(child.child_attribute) print(child.parent_attribute) child.parent_method()

输出如下
I am a child I am a parent Back in my day...

我们看到Child类从Parent类继承了属性和方法。无需任何工作, Parent.parent_method是Child类的一部分。为了获得Parent .__ init __()方法的好处, 我们需要显式调用该方法并传递self。这是因为当我们向Child添加__init__方法时, 我们重写了继承的__init__。
有了这个简短而又不全面的概述, 我们就可以深入了解该职位。
super介绍 在最简单的情况下, 可以使用super函数来替换对Parent .__ init __(self)的显式调用。可以使用super重写第一部分中的介绍示例, 如下所示。请注意, 以下代码块是用Python 3编写的, 早期版本使用的语法略有不同。另外, 由于输出与第一个代码块相同, 因此省略了输出。
class Parent: def __init__(self): self.parent_attribute = 'I am a parent'def parent_method(self): print('Back in my day...')# Create a child class that inherits from Parent class Child(Parent): def __init__(self): super().__init__() self.child_attribute = 'I am a parent'# Create instance of child child = Child()# Show attributes and methods of child class print(child.child_attribute) print(child.parent_attribute) child.parent_method()

老实说, 在这种情况下, super没有给我们带来什么好处(如果有的话)。根据父类的名称, 我们可以保存一些按键, 并且不必将self传递给对__init__的调用。以下是在单继承情况下使用super的一些优缺点。
缺点
可以说在这里使用super会使代码不太明确。减少代码的显式性违反了Python的Zen标准, 即” 显式比隐式更好” 。
优点
有一个可维护性的论点, 即使在单继承中也可以说是super。如果出于任何原因你的子类更改了其继承模式(即, 父类更改了或转移到了多个继承), 则无需查找并替换所有对ParentClass.method_name()的持久引用。使用super将允许所有更改随class语句中的更改一起通过。
super继承和多重继承 在我们进入多重继承和super继承之前… 警告, 这可能变得非常奇怪和复杂。
首先, 什么是多重继承?到目前为止, 示例代码涵盖了从单个父类继承的单个子类。在多重继承中, 有多个父类。子类可以继承2、3、10等父类。
在这里, super的好处变得更加明显。除了保存引用不同父类名称的键击之外, 将super与多个继承模式一起使用还有细微的好处。简而言之, 如果你要使用多重继承, 请使用super。
没有super的多重继承
让我们看一个避免继承的方法, 它避免了修改任何父方法, 而又避免了父方法。
输入
class B: def b(self): print('b')class C: def c(self): print('c')class D(B, C): def d(self): print('d')d = D() d.b() d.c() d.d()

输出如下
b c d

多分辨率订单
考虑到多重继承的概念, 这个输出并不令人惊讶。 D从其父类继承了x和z方法, 现在世界上一切都很好。
那么, 如果B和C都具有相同名称的方法怎么办?这就是所谓的” 多分辨率顺序” 的概念或简称MRO的作用所在。子类的MRO决定了Python将在哪里寻找给定的方法, 以及在发生冲突时将调用哪个方法。
【多重继承和super()简介】让我们来看一个例子。
输入
class B: def x(self): print('x: B')class C: def x(self): print('x: C')class D(B, C): passd = D() d.x() print(D.mro())

输出如下
x: B [< class '__main__.D'> , < class '__main__.B'> , < class '__main__.C'> , < class 'object'> ]

当调用继承的x方法时, 我们只能看到从B继承的输出。通过调用mro类方法, 可以看到D类的MRO。从D.mro()输出中, 我们了解到以下内容:默认情况下, 我们的程序将尝试调用D方法, 然后依次使用B, C, 最后是对象。如果在任何这些地方都找不到它, 那么我们会得到一个错误, 那就是D没有我们要求的方法。
值得注意的是, 默认情况下, 每个类都继承自对象, 并且它位于每个MRO的末尾。
多重继承, super和钻石问题
以下是使用super以有益的方式处理init的MRO的示例。在示例中, 我们创建了一系列文本处理类, 并将它们的功能结合到另一个具有多重继承的类中。我们将创建4个类, 继承的结构将遵循下图的结构。
注意:此结构是出于说明目的, 并且除非有限制, 否则会有更好的方法来实现此逻辑。
多重继承和super()简介

文章图片
这实际上是多重继承的” 钻石问题” 的一个例子。它的名称当然是基于其设计的形状, 也是一个相当混乱的问题。
下面的设计是用super写出来的。
输入
class Tokenizer: """Tokenize text""" def __init__(self, text): print('Start Tokenizer.__init__()') self.tokens = text.split() print('End Tokenizer.__init__()')class WordCounter(Tokenizer): """Count words in text""" def __init__(self, text): print('Start WordCounter.__init__()') super().__init__(text) self.word_count = len(self.tokens) print('End WordCounter.__init__()')class Vocabulary(Tokenizer): """Find unique words in text""" def __init__(self, text): print('Start init Vocabulary.__init__()') super().__init__(text) self.vocab = set(self.tokens) print('End init Vocabulary.__init__()')class TextDescriber(WordCounter, Vocabulary): """Describe text with multiple metrics""" def __init__(self, text): print('Start init TextDescriber.__init__()') super().__init__(text) print('End init TextDescriber.__init__()')td = TextDescriber('row row row your boat') print('--------') print(td.tokens) print(td.vocab) print(td.word_count)

输出如下
Start init TextDescriber.__init__() Start WordCounter.__init__() Start init Vocabulary.__init__() Start Tokenizer.__init__() End Tokenizer.__init__() End init Vocabulary.__init__() End WordCounter.__init__() End init TextDescriber.__init__() -------- ['row', 'row', 'row', 'your', 'boat'] {'boat', 'your', 'row'} 5

首先, 我们看到TextDescriber类继承了类族树的所有属性。由于多重继承, 我们可以” 组合” 多个类的功能。
现在, 让我们讨论来自类的init方法的打印输出:
每个__init__方法仅被调用一次。 TextDescriber类继承自从Tokenizer继承的2个类。为什么没有两次调用Tokenizer .__ init__?
如果我们用老式方法替换所有对super的调用, 最终将有2次对Tokenizer .__ init__的调用。通过我们的模式对super” 思考” 的调用要多一点, 并且跳过了前往A的额外行程。
每个__init__方法都在其他方法完成之前启动。 值得注意的是, 每个__init__的开始和结束的顺序都非常重要, 以防你尝试设置与另一个父类发生命名冲突的属性。该属性将被覆盖, 并且可能变得非常混乱。
在我们的案例中, 我们避免了使用继承的属性命名冲突, 因此一切都按预期进行。
重申一下, 钻石问题会很快变得复杂, 并导致意想不到的结果。在编程的大多数情况下, 最好避免复杂的设计。
我们学到了什么
  • 我们了解了super功能以及如何在单个继承中将其用于替换ParentName.method。这可能是更可维护的做法。
  • 我们了解了多重继承, 以及如何将多个父类的功能传递给单个子类。
  • 我们了解了多分辨率顺序及其在父方法之间存在命名冲突时如何决定多重继承中发生的情况。
  • 我们了解了钻石问题, 并看到了使用super钻石如何导航钻石的示例。

    推荐阅读