Python 类的继承、多继承与派生
继承的概念与本质
在面向对象编程中,继承是一种机制,允许一个类(子类)获取另一个类(父类)的属性和方法。这实现了代码的重用和逻辑的组织。当一个子类继承父类时,它就拥有了父类的所有非私有成员,仿佛这些成员是它自己定义的一样。
- 被继承的类称为父类(或基类、超类)。
- 继承的类称为子类(或派生类)。
以下是一个简单的单继承示例。我们定义一个父类 Vehicle,它包含一些通用的属性和方法。然后,我们创建一个子类 Car,它继承自 Vehicle。
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def start_engine(self):
print(f"{self.brand} {self.model} 的引擎已启动。")
class Car(Vehicle):
# Car 类继承了 Vehicle 的所有属性和方法
pass
my_car = Car("Toyota", "Corolla")
print(my_car.brand) # 输出: Toyota
my_car.start_engine() # 输出: Toyota Corolla 的引擎已启动。
在这个例子中,Car 类是空的,但它能够访问 Vehicle 类的 brand、model 属性和 start_engine 方法。
多继承
Python 支持多继承,这意味着一个子类可以同时继承多个父类。这使得类可以组合来自不同来源的功能。
例如,我们可以定义几个不同的类,每个类代表一种技能,然后创建一个继承所有这些技能的类。
class Engineer:
def code(self):
print("编写代码...")
class Musician:
def play_instrument(self):
print("弹奏乐器...")
class Artist:
def paint(self):
print("绘画...")
class CreativePerson(Engineer, Musician, Artist):
# 这个类同时继承了 Engineer, Musician, 和 Artist 的能力
pass
john = CreativePerson()
john.code() # 输出: 编写代码...
john.play_instrument() # 输出: 弹奏乐器...
john.paint() # 输出: 绘画...
通过多继承,CreativePerson 类的对象 john 能够调用所有三个父类的方法。
名称查找顺序(MRO)
当访问一个属性或方法时,Python 会按照特定的顺序在类和对象中查找。这个顺序被称为方法解析顺序(MRO)。
单继承的查找顺序
在单继承中,查找顺序非常直接:先在对象自身查找,然后在其类中查找,最后在父类中查找。
class Parent:
name = "父类"
class Child(Parent):
name = "子类"
obj = Child()
obj.name = "对象自身"
print(obj.name) # 输出: 对象自身
print(Child.name) # 输出: 子类
print(Parent.name) # 输出: 父类
多继承的查找顺序
多继承的查找顺序更为复杂,遵循 C3 线性化算法。对于菱形继承结构(多个类最终继承自同一个基类),Python 会采用广度优先的查找顺序。我们可以使用类的 mro() 方法来查看其方法解析顺序。
class G:
name = "G"
class A(G):
name = "A"
class B(G):
name = "B"
class C(G):
name = "C"
class D(A):
name = "D"
class E(B):
name = "E"
class F(C):
name = "F"
class S(D, E, F):
pass
print(S.mro())
# 输出: [<class '__main__.S'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.G'>, <class 'object'>]
这个顺序表明,当查找 S 类的属性时,Python 会先在 S 中查找,然后是 D,接着是 A,依此类推。
派生方法与 super 关键字
派生是指在子类中修改或扩展父类方法的行为。使用 super() 关键字可以方便地调用父类的方法。
例如,我们有一个 Device 基类,并创建一个 Phone 子类。我们想在子类的构造函数中调用父类的构造函数,并添加新的属性。
class Device:
def __init__(self, brand, model):
self.brand = brand
self.model = model
print("设备初始化完成。")
class Phone(Device):
def __init__(self, brand, model, storage):
super().__init__(brand, model) # 调用父类的构造函数
self.storage = storage
print("手机初始化完成。")
my_phone = Phone("Apple", "iPhone 15", "256GB")
print(my_phone.__dict__)
# 输出: {'brand': 'Apple', 'model': 'iPhone 15', 'storage': '256GB'}
派生用法示例
我们可以通过派生来修改父类的行为。例如,我们可以创建一个自定义的字典类,限制其键必须是字符串。
class StrictDict(dict):
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError("键必须是字符串")
super().__setitem__(key, value)
my_dict = StrictDict()
my_dict["name"] = "Alice" # 正常
print(my_dict)
try:
my_dict[123] = "Bob" # 会引发 TypeError
except TypeError as e:
print(e) # 输出: 键必须是字符串
派生方法实战:自定义 JSON 编码
Python 的 json 模块默认不支持序列化所有数据类型,例如 datetime 对象。我们可以通过继承 json.JSONEncoder 并重写其 default 方法来实现自定义的序列化逻辑。
import json
from datetime import datetime, date
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
# 如果对象是 datetime 类型,则格式化为字符串
if isinstance(obj, datetime):
return obj.strftime('%Y-%m-%d %H:%M:%S')
# 如果对象是 date 类型,则格式化为字符串
elif isinstance(obj, date):
return obj.strftime('%Y-%m-%d')
# 对于其他不支持的类型,调用父类的方法(会引发 TypeError)
return super().default(obj)
data = {
"event_time": datetime.now(),
"event_date": date.today(),
"message": "这是一个测试"
}
# 使用自定义的编码器进行序列化
json_str = json.dumps(data, cls=CustomJSONEncoder)
print(json_str)
# 输出类似: {"event_time": "2023-10-27 10:30:00", "event_date": "2023-10-27", "message": "这是一个测试"}