继承
继承是面向对象编程中的一个重要概念。通过继承,我们可以让一个类获取到其他类中的属性和方法,避免编写重复性的代码,并且符合开闭原则(OCP)。继承是使一个类扩展的常用方式。
定义一个类 Animal
我们先定义一个类 Animal,这个类中有两个方法 run() 和 sleep(),表示动物会跑和睡觉。
class Animal: def run(self): print('动物会跑~~~') def sleep(self): print('动物睡觉~~~')
定义一个类 Dog
现在,我们想定义一个类 Dog,这个类除了能够跑和睡觉外,还能够汪汪叫。我们可以直接修改 Animal 类,在其中添加 bark() 方法,但这样会违反 OCP 原则。更好的方式是通过继承。
class Dog(Animal): def bark(self): print('汪汪汪~~~') def run(self): print('狗跑~~~~')
在上面的代码中,我们使用 class Dog(Animal): 来指定 Dog 类继承自 Animal 类。这样,Dog 类就能够直接获取到 Animal 类的属性和方法。我们还可以重写 Animal 类中的方法,如在 Dog 类中重新定义了 run() 方法。
创建对象并调用方法
现在我们可以创建 Dog 类的对象 d = Dog(),并调用它的方法,比如 d.run()、d.sleep() 和 d.bark()。
d = Dog() d.run() # 输出:狗跑~~~~ d.sleep() # 输出:动物睡觉~~~ d.bark() # 输出:汪汪汪~~~
类之间的关系
通过使用继承,我们可以建立类之间的层次关系。在创建类时,如果省略了父类,则默认父类为 object。object 是所有类的父类,所以所有类都继承自 object。
class Person(object): pass
我们可以使用 issubclass() 函数检查一个类是否是另一个类的子类,如 issubclass(Animal, Dog) 返回 False,而 issubclass(Animal, object) 返回 True。
我们还可以使用 isinstance() 函数来检查一个对象是否是一个类的实例。如果这个类是这个对象的父类,也会返回 True。所有的对象都是 object 的实例。
print(isinstance(d, Dog)) # 输出:True print(isinstance(d, Animal)) # 输出:True print(isinstance(d, object)) # 输出:True print(isinstance(print, object)) # 输出:True
继承使得类之间的关系更加清晰,并且方便代码的复用和扩展。
多重继承
除了单一继承外,Python 还支持多重继承,即一个子类可以从多个父类中继承属性和方法。这为我们提供了更大的灵活性,使得代码的组织和复用更加方便。
定义一个类 Hashiqi
假设我们还有一个类 Hashiqi,表示一种特殊的狗,它除了具备狗的基本行为外,还有自己特有的方法 fan_sha(),表示傻傻的哈士奇。
class Hashiqi(Dog): def fan_sha(self): print('我是一只傻傻的哈士奇')
在上面的代码中,我们定义了一个 Hashiqi 类,它继承自 Dog 类。因此,Hashiqi 类不仅能够拥有 Animal 类和 Dog 类中的属性和方法,还具备自己特有的 fan_sha() 方法。
创建对象并调用方法
现在我们可以创建 Hashiqi 类的对象 h = Hashiqi(),并调用它的方法,比如 h.run()、h.sleep()、h.bark() 和 h.fan_sha()。
h = Hashiqi() h.run() # 输出:狗跑~~~~ h.sleep() # 输出:动物睡觉~~~ h.bark() # 输出:汪汪汪~~~ h.fan_sha() # 输出:我是一只傻傻的哈士奇
通过多重继承,我们可以实现更灵活和具有复杂关系的类结构。在创建子类时,只需指定多个父类,并且子类可以直接获取到所有父类中的属性和方法,从而减少了代码的冗余。
继承是面向对象编程的重要特性之一,它能够提高代码的可读性、可维护性和可扩展性,并符合开闭原则。在设计类的时候,我们应该充分考虑继承关系,遵循良好的代码组织和设计原则。
当一个类继承了多个父类时,可能会遇到命名冲突的问题。比如,如果两个父类都有相同名称的方法或属性,那么在子类中调用这个名称时会出现歧义。为了解决这个问题,Python 提供了方法解析顺序(Method Resolution Order,简称MRO)。
方法解析顺序(MRO)
在 Python 中,每个类都有一个方法解析顺序,即它继承的父类被搜索的顺序。可以通过 类名.__mro__ 或者 类名.mro() 来查看方法解析顺序。下面是一个例子:
class A: def method(self): print("A") class B(A): def method(self): print("B") class C(A): def method(self): print("C") class D(B, C): pass print(D.__mro__) print(D.mro())
运行上面的代码,会输出以下结果:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
从输出结果可以看出,方法解析顺序是 D -> B -> C -> A -> object。也就是说,在 D 类的实例上调用 method() 方法时,会按照该顺序依次查找,并执行第一个匹配到的方法。
super() 函数
super() 函数是用于调用父类方法的一种方式。可以使用 super().方法名() 的形式来调用父类的方法,而不需要显式地指定父类的名称。比如:
class A: def method(self): print("A") class B(A): def method(self): super().method() print("B") b = B() b.method()
输出结果为:
A B
在上述代码中,B 类继承自 A 类,并在自己的 method() 方法中使用 super().method() 这一方式调用了父类 A 中的 method() 方法,并在其后打印了 “B”。
使用 super() 函数,可以保证在多重继承时按照方法解析顺序依次调用父类的方法,从而避免命名冲突和歧义。
当在多重继承中存在钻石继承(diamond inheritance)的情况时,为了避免方法重复调用和冗余代码,Python 使用 C3 线性化算法来确定方法解析顺序(Method Resolution Order,MRO)。
C3 线性化算法
C3 线性化算法通过合并多个父类的线性化顺序,生成一个满足以下条件的线性化列表:
- 子类永远在父类前面。
- 如果一个类在列表中的前面出现,那么它的所有父类也都在其前面。
- 如果多个父类都在一个类的后面,那么它们的顺序保持不变。
这样,在方法解析顺序中,每个类的方法只会被调用一次,避免了重复调用和冗余代码。
示例
以下是一个钻石继承的示例:
class A: def method(self): print("A") class B(A): pass class C(A): def method(self): print("C") class D(B, C): pass d = D() d.method()
在上述代码中,类 D 继承了 B 和 C,而 B 和 C 都继承了 A。当我们创建 D 的实例并调用 method() 方法时,根据 C3 线性化算法得到的方法解析顺序是 D -> B -> C -> A,因此输出结果为 “C”。
super() 函数和钻石继承
在使用 super() 函数时,Python 会根据 MRO 确定的方法解析顺序来调用父类的方法。在钻石继承的情况下,super() 函数会按照 MRO 的顺序依次调用每个父类的方法,并保证每个方法只被调用一次。
class A: def method(self): print("A") class B(A): def method(self): super().method() print("B") class C(A): def method(self): super().method() print("C") class D(B, C): pass d = D() d.method()
运行上述代码,输出结果为:
A C B
在该示例中,类 D 的方法解析顺序是 D -> B -> C -> A。当调用 d.method() 方法时,super().method() 会依次调用 B、C 和 A 类的 method() 方法。通过 MRO,每个类的方法只会被调用一次,避免了重复调用和冗余代码。
经典类和新式类
在 Python 2.x 版本中,存在经典类和新式类的概念。经典类是指没有显式继承自 object 的类,而新式类则是显式继承自 object 的类。
在经典类中,Python 使用深度优先搜索的方法来解析方法调用顺序。这种方法存在一些问题,比如方法重复调用、无法实现多重继承时的方法解析顺序等。
在新式类中,Python 使用 C3 线性化算法来解析方法的调用顺序。这种方法可以保证方法只被调用一次,并且解决了多重继承时方法解析顺序不确定的问题。
为了更好地兼容新旧版本的 Python,在 Python 2.3 版本中引入了一个特殊语法,即在定义类时使用 class ClassName(object): 的形式,从而将所有类都定义为新式类。
在 Python 3.x 版本中,则已经默认将所有类定义为新式类,无需显式继承自 object。
总结
Python 中的多重继承给程序员提供了更灵活的设计选择,但也带来了一些挑战。为了避免命名冲突和歧义,在多重继承中需要正确地设置方法解析顺序(MRO),从而保证方法调用的正确性和效率。
Python3.x 已经默认将所有类定义为新式类,并使用 C3 线性化算法来解决多重继承的问题,不再需要特别注意 MRO 的设置。
在 Python 中,钻石继承(diamond inheritance)是指一个子类同时继承自两个有共同父类的类,形成了一个菱形的继承结构。这种继承结构会引发一些问题,例如方法重复调用和冗余代码。
为了解决钻石继承带来的问题,Python 使用 C3 线性化算法来确定方法的解析顺序(Method Resolution Order,MRO)。具体步骤如下:
- 按照类的声明顺序,生成一个拓扑排序的列表(DAG)。
- 在拓扑排序的列表中,检查每个节点的父类列表,并将其父类所在的位置移动到它自身的前面。这样,可以保证子类在父类之前。
- 对于多个父类同时出现在同一个节点之后的情况,需要按照它们在基类列表中的顺序保持不变。
通过使用 C3 线性化算法,Python 可以避免方法重复调用和冗余代码的问题。此外,Python 会在类的定义过程中自动计算 MRO,并将其存储在特殊属性 __mro__ 中,供开发者查看。
需要注意的是,在实际使用中,当存在钻石继承的情况时,可以通过合理设计类的继承关系和使用 super() 函数来避免问题的出现。合理利用多态性、组合等技术也可以减轻继承带来的复杂性。