前言
在面向对象编程(OOP)中,继承是一个强大且重要的概念。它允许我们基于现有类创建新类,从而实现代码的重用和结构化。本文将深入探讨Python中的继承,包括其基本概念、使用方法以及高级技巧。
什么是继承
继承是一种机制,通过它,一个类(子类)可以继承另一个类(父类)的属性和方法。子类不仅可以使用父类的所有功能,还可以增加新的功能或者重写父类的功能。
基本概念
在Python中,继承通过类定义中的括号来实现,Python默认继承Object
class Parent:
# 父类的属性和方法
class Child(Parent):
# 子类的属性和方法
示例
我们通过一个简单的例子,来看一下继承的使用方法
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
def explain(self):
return f"{self.name}:I am a animal"
class Dog(Animal):
def speak(self):
return f"{self.name} says wow!"
class Cat(Animal):
def speak(self):
return f"{self.name} says miao!"
dog = Dog("xiaohei")
cat = Cat("dabai")
print(dog.speak()) # 输出: xiaohei says wow!
print(cat.speak()) # 输出: dabai says miao!
print(dog.explain()) # 输出: xiaohei:I am a animal
print(cat.explain()) # 输出: dabai:I am a animal
上面代码中Dog和Cat都继承自Animal,重写了speak,但也可以直接使用父类的explain函数。
super()
函数
在子类中调用父类的方法,可以使用super()函数。这对于扩展或重写父类的方法特别有用。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Animal sound"
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
def speak(self):
return super().speak() + " wow!"
dog = Dog("xiaohei")
print(dog.speak()) # 输出: Animal sound wow!
多重继承
Python还支持多重继承,即一个类可以继承多个父类。多重继承是一个难点,有些语言不支持多重继承,虽然这在某些情况下非常有用,但也需要谨慎使用,以避免复杂性和潜在的冲突。
class Parent1:
def method1(self):
print("Parent1 method1")
class Parent2:
def method2(self):
print("Parent2 method2")
class Child(Parent1, Parent2):
pass
child = Child()
child.method1() # 输出: Parent1 method1
child.method2() # 输出: Parent2 method2
多重继承代码看起来确实很简单,和单继承差不多,但实际应用中可能会更为复杂。我们再来看一个更为复杂的多继承的示例(代码引用《流畅的Python》第二版)
class Root:
def ping(self):
print(f'{self}.ping() in Root')
def pong(self):
print(f'{self}.pong() in Root')
def __repr__(self):
cls_name = type(self).__name__
return f'<instance of {cls_name}>'
class A(Root):
def ping(self):
print(f'{self}.ping() in A')
super().ping()
def pong(self):
print(f'{self}.pong() in A')
super().pong()
class B(Root):
def ping(self):
print(f'{self}.ping() in B')
super().ping()
def pong(self):
print(f'{self}.pong() in B')
class Leaf(A,B):
def ping(self):
print(f'{self}.ping() in Leaf')
super().ping()
leaf1 = Leaf()
print('---------ping---------')
leaf1.ping()
print('---------pong---------')
leaf1.pong()
上面首先定义了基类Root,然后A,B继承了Root,并重写了基类的方法,唯一不同的是,在B的pong方法中,没有调用super(),Leaf又继承了A和B,重写了ping,运行代码,看一下结果
---------ping---------
<instance of Leaf>.ping() in Leaf
<instance of Leaf>.ping() in A
<instance of Leaf>.ping() in B
<instance of Leaf>.ping() in Root
---------pong---------
<instance of Leaf>.pong() in A
<instance of Leaf>.pong() in B
Leaf对象调用ping()方法时,调用顺序是Leaf -> A -> B -> Root,而调用pong()时,调用顺序是 Leaf -> A -> B,没有调用Root,如果将Leaf 定义为
class Leaf(B,A):
def ping(self):
print(f'{self}.ping() in Leaf')
super().ping()
调用pong(),你会发现,只会答应 <instance of Leaf>.pong() in B
。也就是 Leaf -> B
不知你发现一点端倪没有,在B的pong()方法中,没有调用super(), 导致调用结果大不相同,前面说过子类可以通过super()调用父类方法,但在多重继承中,调用super()的方法叫协作方法,利用协作方法可以实现协作多重继承。我的理解就是,在多重继承中,是否调用super(),决定会不会继续调用下一个基类的方法,从上面的示例中,我们可以发现子类执行基类方法时,在从括号的左到右的顺序执行的
上面示例中,A的ping,pong和B的ping都是协作方法,B的pong不是,所以当Leaf的对象执行pong时,执行到B这里后,就戛然而止了。可能会有点绕,但自己复现以下代码,多执行几次,调换一下Leaf中继承的顺序,应该会有一定程度的理解。
混入类
混入类在多重继承中会连同其他类一起被子类化。混入类不能作为具
体类的唯一基类,因为混入类不为具体对象提供全部功能,而是增加
或定制子类或同级类的行为。
上面提到super()的作用,就是为了接下来要谈论的混入类,下面来看一下代码
class TestMixin:
def speak(self):
print(f'{self}.speak() in TestMixin')
super().speak()
class A:
def speak(self):
print(f'{self}.speak() in A')
class Leaf(TestMixin,A):
def speak(self):
print(f'{self}.speak() in Leaf')
super().speak()
leaf1 = Leaf()
leaf1.speak()
在没有运行代码之前,你猜测结果是什么呢?我最开始认为代码会报错,因为TestMixin中,super默认是Object,应该没有实现speak()方法,应该要报错呀,但实际结果出乎意料
<__main__.Leaf object at 0x000001EDA24AEDD0>.speak() in Leaf
<__main__.Leaf object at 0x000001EDA24AEDD0>.speak() in TestMinin
<__main__.Leaf object at 0x000001EDA24AEDD0>.speak() in A
Leaf调用执行顺序为 Leaf -> TestMixin -> A ,不仅没有报错,还正常调用了基类方法,同时要注意到,混入类不能独立使用,也不能单独被继承的。
一般情况下,使用混入类都要求
- 避免状态共享: 混入类通常不应该有共享状态,避免不同类实例之间的状态混淆。
- 单一职责: 每个汇入类应该只提供一个单一的功能,保持简洁和易于组合。
在Django和Tkinter中,大量运用了多重继承和混入类的使用,这里不展开了,有兴趣的可以去查阅一下相关资料。
这里介绍多重继承和混入类,主要是关注**super()**在其中的作用,简单来说,在多重继承中,super()就像一个传递者一样,让子类能依序调用多个基类的方法。
继承的高级技巧
抽象基类
抽象基类定义了一组方法,子类必须实现这些方法。和一些语言里面的接口其实是一个意思Python的abc
模块提供了抽象基类的支持。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def eat(self):
print('eat')
def speak(self):
return "wow!"
dog = Dog()
print(dog.speak()) # 输出: wow!
实现上和继承是一样的,只是在父类方法中使用了@abstractmethod
,但子类中必须要实现这个方法,不重写方法,就会报错,你可以尝试一下,我举得抽象基类就是python的接口表现形式
方法解析顺序(MRO)
在多重继承中,方法解析顺序(MRO)决定了在调用方法时搜索的顺序。可以使用ClassName.mro()
查看类的MRO。多重继承和混入类的代码中,你们一定也注意到了子类执行父类时的顺序,Python中提供ClassName.mro()
,可以查看顺序。我们打印一下多重继承示例中,Leaf类的mro
print(Leaf.mro())
# 输出结果:[<class '__main__.Leaf'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>]
可以看到输出是 Leaf -> A -> B -> Root -> object,子类搜索基类的顺序从左到右,从下到上。
总结
继承是Python面向对象编程中的一个核心概念,它使得代码更加模块化和可重用。通过理解和正确使用继承,我们可以创建更加复杂和功能丰富的程序。同时,注意使用super()函数、抽象基类和理解方法解析顺序,可以让我们在处理复杂的继承关系时更加得心应手。多重继承和混入类是一个难点,不仅难以理解,还容易造成不必要的错误,在实际开发中慎用。其实绝大多数情况下,开发者几乎不会用到这两个功能了,但也有必要了解一下