Python从入门到实战(七):类、对象与继承
目录一、面向对象概述1. 面向过程与面向对象2. 类与对象概念3. 实例与实例化二、定义类与创建对象1. 基础语法2. __init__构造方法3. self 关键字三、属性1. 实例属性2. 类属性3. __dict__ 属性4. 属性查找优先级四、方法1. 实例方法2. 类方法3. 静态方法五、继承1. 继承基本概念2. 继承语法结构3. 继承底层机制4. 属性与方法继承案例六、方法重写1. 基本概念super 函数2. 方法重写案例3. 方法重写注意事项七、继承关系判断1. isinstance() 函数2. issubclass() 函数3. 为什么不能使用 type() 进行类型判断八、多重继承1. 多重继承的语法实现2. 菱形继承与命名冲突3. MRO 与 C3 线性化算法4. 多重继承下的 super 行为5. 多重继承注意事项总结一、面向对象概述当软件系统的业务复杂度达到一定规模时传统的面向过程流控制会导致代码的维护成本呈指数级上升为了构建高内聚、低耦合的现代软件系统我们需要引入一种全新的编程范式——面向对象编程。本篇将系统拆解面向对象的核心概念并探讨其底层的设计哲学1. 面向过程与面向对象理解面向对象编程首先需要厘清它与面向过程编程在解决问题视角上的差异面向过程POP以算法和执行步骤为核心。其核心思维是 怎么做。程序将复杂的业务逻辑拆解为一系列按顺序执行的函数数据通过参数在各个函数之间跳转局限性当系统规模扩大时由于数据与函数是分离的对数据结构的任何微小改动都可能引发大面积的函数重构系统维护极为脆弱面向对象OOP以数据和实体对象为核心。其核心思维是 谁来做。程序将现实世界中的事物抽象为独立的 对象将数据属性与操纵这些数据的行为方法捆绑在一起形成一个完整的结构优势通过封装、继承和多态提供高复用性、可扩展性以及对复杂系统的控制力2. 类与对象概念为了准确理解我们可以将其映射到现实中的关系建筑图纸与实体房屋类Class相当于一张精密的建筑图纸。图纸定义了房屋的结构高矮、长宽、房间布局、承重墙位置但图纸本身只是一个抽象的规范并不占据实际的空间也不能直接居住对象Object相当于依据该图纸建造出来的真实房屋。它是实实在在占据物理空间、具备实际使用功能的实体手机型号设计与手中持有的物理手机类Class相当于某个品牌手机的出厂参数型号和生产规范例如定义了它应该具备多大容量的电池、什么型号的处理器、以及支持拍摄功能对象Object相当于你此时此刻手中拿着的、正在使用的这台物理手机。它拥有属于它自己的剩余电量、专属的 IMEI 串号以及内部存储的具体数据通过这两组映射可以看出一个类可以作为模板批量创造出成百上千个功能相同的对象。这些对象共享相同的行为逻辑方法但各自拥有独立的内部状态数据属性3. 实例与实例化在面向对象的语境中还有两个高频出现的术语需要精准界定实例化指由抽象的类产生具体的对象的过程。在代码层面就是指调用某种特定机制在系统内存中为该类分配空间并完成初始化赋值的行为实例通过 实例化 过程产生的具体对象就被称为该类的一个实例在绝大多数语境下对象Object与 实例Instance是完全等价的概念。如果非要寻找细微差别对象是一个更加宏观的实体代称而实例则更加侧重强调该实体与其所属的母类之间...的实例的物理血缘关系二、定义类与创建对象在 Python 中实现面向对象编程的第一步是声明 类作为模板进而通过该类在内存中分配空间并构建具体的对象实例1. 基础语法类的定义Python 使用内置关键字 class 来定义一个类。根据 PEP 8 编码规范类名应当组合多个单词并采用大驼峰命名法即每个单词的首字母均大写不使用下划线分隔class SmartPhone: 定义一个智能手机类 # 类的内部可以包含属性定义与方法定义 pass对象的创建创建对象的语法形式类似于调用一个没有参数的函数对象名 类名()# 实例化两个独立的手机对象 phone_a SmartPhone() phone_b SmartPhone() print(type(phone_a)) # 输出class __main__.SmartPhone print(id(phone_a)) # 输出 phone_a 在内存中的唯物理一地址 print(id(phone_b)) # 输出一个完全不同的物理地址证明两者的独立性2. __init__构造方法在创建对象时我们通常需要为不同的对象赋予不同的初始状态例如赋予不同的手机品牌、颜色或价格。为此Python 引入了双下划线包围的特殊方法——__init__ 方法__init__ 方法被称为构造方法或初始化方法。它的核心职能是当对象被实例化出来时Python 解释器会自动、隐式地调用该方法用于执行对象属性的初始化赋值class SmartPhone: def __init__(self, brand, price): # 在此处为新诞生顺产生的对象绑定初始属性 self.brand brand self.price price # 创建 SmartPhone 类的对象 my_phone SmartPhone(Apple, 8999)3. self 关键字为什么定义类的方法时第一个参数必须是 self从底层的物理结构来看一个类被加载后其内部的方法代码块在内存中只有一份副本。当有成百上千个不同的对象同时调用同一个方法时Python 解释器必须知道当前正在操作哪一个具体的对象实体self 关键字本质上是一个内存指针。它代表当前正在被创建或正在调用方法的那个对象实例本身。在 __init__ 方法中self.brand brand 的真实含义是将传入的 brand 数据写入到当前这个新诞生对象的专属内存空间中为什么在实例化创建对象时不需要手动传入 self这是因为 Python 解释器在底层为我们做了隐式自动传递。当我们执行代码 my_phone SmartPhone(Apple, 8999) 时底层的实际运行流程如下Python 在堆内存中开辟一块空间给新对象Python 自动将这个新对象的内存首地址作为第一个参数隐式传递给 __init__ 的 self 形参传入的 Apple 和 8999 则依次对应传给 brand 和 price也就是说代码层面的 my_phone SmartPhone(Apple, 8999) 在解释器内部等价于SmartPhone.__init__(my_phone, Apple, 8999)}三、属性在面向对象编程中属性 用于定义对象的状态数据。Python 的属性管理机制非常灵活根据作用域和生命周期的不同属性主要分为实例属性与类属性。理解这两者的区别是编写高效、无 Bug 面向对象代码的关键1. 实例属性实例属性是绑定在具体对象实例上的属性。每个对象实例都拥有一份独立的实例属性空间互不干扰定义通常在类的 __init__ 构造方法中通过 self.属性名 值 的形式进行声明和赋值生命周期随着对象的创建而诞生随着对象的销毁而消失访问方式必须通过对象名进行访问如对象名.属性名class Account: def __init__(self, username, balance): self.username username # 实例属性 self.balance balance # 实例属性 acc1 Account(Alice, 5000) acc2 Account(Bob, 8000) # 每个实例的属性完全独立 print(acc1.username) # 输出Alice print(acc2.username) # 输出Bob2. 类属性类属性是属于类对象本身的属性。它被该类所有的实例对象所共同拥有在内存中只有一份副本定义直接定义在类体内部、所有方法之外生命周期随着类的加载而诞生在程序运行期间一直存在直到程序结束访问方式推荐直接通过类名访问类名.属性名也可以通过对象名间接访问class Account: # 类属性定义银行的名称所有账户共享此信息 bank_name ICBC def __init__(self, username): self.username username # 推荐直接通过类名访问类属性 print(Account.bank_name) # 输出ICBC acc Account(Charlie) # 允许通过实例对象间接访问类属性 print(acc.bank_name) # 输出ICBC3. __dict__ 属性Python 的类和实例在底层基于字典进行动态管理每个类对象和实例对象内部都包含一个内置的只读属性 __dict__。它是一个标准的 Python 字典里面存储了该对象当前拥有一切属性和对应的值通过观察 __dict__我们可以清晰地看到实例属性与类属性的底层存储分离# 观察类对象的 __dict__ print(Account类的属性字典, Account.__dict__) acc_demo Account(David) # 观察实例对象的 __dict__ print(acc_demo实例的属性字典, acc_demo.__dict__)从上述结果可以看出实例对象的 __dict__中默认并不包含类属性。实例之所以能读取到类属性全赖于 Python 底层的属性查找优先级机制4. 属性查找优先级当获取或修改属性值时Python 解释器在底层的物理逻辑如下属性读取逻辑当执行 print(acc_demo.bank_name) 时解释器首先进入 acc_demo.__dict__ 寻找是否存在键名为 bank_name 的实例属性若没有找到解释器会去该实例对应的类属性字典 Account.__dict__ 中寻找若类属性字典中存在则返回对应的值若最终都找不到则抛出异常属性修改问题如果尝试通过实例对象去修改类属性会引发一个隐蔽的行为acc_x Account(UserX) acc_y Account(UserY) # 尝试通过实例 acc_x 修改类属性 acc_x.bank_name CCB print(Account.bank_name) # 输出ICBC类属性并未改变 print(acc_y.bank_name) # 输出ICBC其他实例读取类属性也未改变 print(acc_x.bank_name) # 输出CCB 只有 acc_x 改变了底层逻辑当执行 acc_x.bank_name CCB 时根据 Python 的赋值规则该操作并不会去修改 Account.__dict__ 中的内容而是在 acc_x.__dict__ 动态创建了一个同名的实例属性此时两个对象的属性字典状态如下当再次读取 acc_x.bank_name 时由于在实例属性字典中第一步就命中了 bank_name所以直接返回了新创建的实例属性 CCB。这种现象在开发中被称为属性遮蔽结论如果需要修改类属性必须显式地通过类名进行操作即执行Account.bank_name CCB。这样才能确保所有实例共享的全局数据被同步更新四、方法在面向对象的设计中方法用于定义对象的行为逻辑。Python 提供了灵活的方法声明机制根据行为绑定的主体不同方法主要分为实例方法、类方法和静态方法。正确选择方法类型能够使类的逻辑划分更加严密1. 实例方法实例方法是类中最普通、使用频率最高的方法类型。它用来表达具体对象实例的行为语法特征不需要任何特殊的修饰符。第一个参数必须是 self用以接收调用该方法的实例对象访问权限通过 self 指针实例方法既可以读取或修改专属的实例属性也可以读取或修改公共的类属性调用方式推荐通过对象实例直接调用class Connection: # 类属性 timeout 30 def __init__(self, ip): self.ip ip # 实例属性 # 实例方法 def connect_server(self): # 既能访问实例属性也能访问类属性 print(f正在 {self.ip} 服务器建立连接超时时间 {self.timeout} )2. 类方法类方法是属于类对象本身的方法。它通常用于操作类属性或者作为一种 工厂方法 来创建特定配置的类实例语法特征在方法上方必须显式声明classmethod 修饰符。第一个参数固定为 cls代表当前类对象本身访问权限由于类方法在调用时不会隐式接收实例对象因此它只能访问和修改类属性无法访问任何实例属性调用方式推荐直接通过类名调用也可以通过对象间接调用class Connection: timeout 30 def __init__(self, ip): self.ip ip # 类方法 classmethod def set_timeout(cls, new_timeout): # 通过 cls 指针安全地修改类属性 cls.timeout new_timeout print(f全局超时时间已修改为 {cls.timeout} 秒。) # 无需创建实例直接通过类名调用 Connection.set_timeout(60)3. 静态方法静态方法本质上是托管在类的命名空间内部的普通函数。它与类或具体的实例在数据层面没有任何耦合关系通常用于存放某些辅助性的工具函数语法特征在方法上方必须显式声明staticmethod 修饰符。没有强制性的第一个参数不需要 self 或 cls访问权限静态方法无法直接访问实例属性也无法直接访问类属性。如果需要使用外部数据必须通过参数显式传递进来调用方式通常直接通过类名调用class Connection: # 静态方法纯粹的辅助性逻辑判断 IP 格式是否合法 staticmethod def validate_ip(ip_str): # 与类和实例的属性无关仅处理传入的参数 if ip_str.count(.) 3: return True return False # 直接通过类名调用工具方法 is_valid Connection.validate_ip(192.168.1.1) print(fIP格式检测结果{is_valid}) # 输出True方法第一个参数能访问实例能访问类实例方法self√√类方法cls×√静态方法无××五、继承在现实业务开发中随着系统功能的扩张我们会发现许多不同的类之间存在着大量重复的属性和定义。如果为每个类都独立编写一套相同的代码不仅会导致软件结构臃肿也会使后期的系统维护演变为灾难。为了解决这些问题面向对象引入了继承Inheritance1. 继承基本概念假设我们需要开发一个校园教务管理系统系统中需要定义两个实体类Teacher教师类和 Student学生类Teacher 拥有属性姓名、年龄、工号方法吃饭、授课Student 拥有属性姓名、年龄、学号方法吃饭、学习通过这两个类可以发现它们在数据结构和行为特征上存在着明显的交集。重合的部分姓名、年龄、吃饭行为本质上是 人类 这一通用实体的共有特征为了消除这种结构上的冗余我们可以将这些共有特征向上抽离定义一个通用的父类亦称基类 或超类。而具体的 Teacher 和 Student 只需要声明与该父类的关联即可作为子类亦称派生类 自动获取这些通用特征。这种设计思想就是继承2. 继承语法结构Python 支持通过在类名后面的小括号中指定另一个类名来实现继承。其标准的语法声明如下# 声明通用的父类 class Person: def __init__(self, name, age): self.name name self.age age def eat(self): print(f{self.name} 正在用餐。) # 声明子类 Teacher继承自 Person class Teacher(Person): # 子类专有的行为 def teach(self): print(f{self.name} 老师正在讲台上授课。) # 声明子类 Student继承自 Person class Student(Person): # 子类专有的行为 def study(self): print(f{self.name} 学生正在教室里听课。)3. 继承底层机制在子类 Teacher 的内部明明没有写过 eat 方法也没有写过 __init__ 构造方法为什么实例化的子类对象可以直接调用它们呢这背后的物理根源依然在于我们上一章提到的属性字典查找路径机制当执行如下代码时t Teacher(张华, 35) t.eat()Python 解释器在底层的执行流程如下寻找构造方法当执行 Teacher(张华, 35) 时解释器首先寻找 Teacher.__dict__ 中是否存在 __init__ 方法。由于 Teacher 类中没有定义解释器会顺着继承链自动向上检索其父类 Person.__dict__。在父类中成功命中后调用父类的构造方法完成了 self.name 和 self.age 的属性绑定即属性继承的实现寻找行为方法当执行 t.eat() 时解释器首先在实例 t.__dict__ 中寻找该方法未命中随后去子类 Teacher.__dict__ 中寻找依然未命中最后解释器再次沿着继承路径向上追溯到父类 Person.__dict__成功命中并执行该方法即方法继承的实现也就是说子类并不需要将父类的代码物理复制到自己的内存空间里而是通过一套清晰的向上寻址在运行时动态复用了父类的方法和属性空间4. 属性与方法继承案例下面通过完整的运行案例验证子类在完全不重写父类基础的情况下对父类资源的复用# 实例化派生类子类对象 # 尽管 Student 没有写 __init__但它自动继承并复用了 Person 的构造方法 student Student(李明, 19) # 1. 验证属性继承成功读取父类构造方法绑定的属性 print(f学生姓名{student.name}) # 输出李明 print(f学生年龄{student.age}) # 输出19 # 2. 验证方法继承成功调用父类中声明的行为 student.eat() # 输出李明 正在用餐。 # 3. 验证子类专有行为成功调用自身独有的方法 student.study() # 输出李明 学生正在教室里听课。通过继承子类能够在不改变父类原有物理结构的前提下通过引入专有的方法实现对系统功能的自适应扩展。这完美契合了软件工程中经典的开闭原则Open-Closed Principle——对扩展开放对修改关闭六、方法重写虽然继承为子类提供了复用父类代码的便利但在实际业务中父类定义的通用行为往往无法完全满足某个特定子类的个性化需求。此时子类需要对父类的方法进行重新改造这被称为方法重写Method Overriding1. 基本概念方法重写是指子类重新定义一个与父类在函数名称、参数列表上完全相同的方法从底层属性字典的视角来看一旦子类在其自身的 __dict__ 中注册了与父类同名的方法函数当实例对象调用该方法时Python 解释器在子类字典中就会直接命中从而拦截了向上追溯到父类字典的过程。这种机制使得子类能够表现出区别于父类的特定行为super 函数在进行方法重写时子类往往并不是要完全抛弃父类的原有逻辑而是在保留父类基础功能的同时扩展出属于子类的特有业务逻辑为了避免在子类中手工重复编写父类的逻辑Python 提供了内建的super() 函数功能定义super() 返回一个代表父类的代理对象允许子类在重写的方法内部显式地调用父类中被遮蔽的同名方法物理意义它确保了父类的初始化代码或核心逻辑能够被安全地执行是维护继承链条完整性的关键纽带2. 方法重写案例在软件工程中重写最经典的应用场景是构造方法的扩展__init__ 重写以及核心业务行为的特异化下面通过 员工 与 经理 的场景来演示这一机制class Employee: 父类普通员工类 def __init__(self, name, base_salary): self.name name self.base_salary base_salary def get_salary(self): 计算薪资的通用方法 return self.base_salary def work(self): print(f员工 {self.name} 正在处理日常业务。) class Manager(Employee): 子类经理类 def __init__(self, name, base_salary, bonus): # 1. 核心应用一利用 super() 调用父类的构造方法完成基础属性的绑定 super().__init__(name, base_salary) # 2. 绑定经理类特有的专属属性 self.bonus bonus def get_salary(self): # 3. 核心应用二重写薪资计算逻辑 # 经理的薪资 父类算出的基本薪资 专有的奖金 actual_salary super().get_salary() self.bonus return actual_salary def work(self): # 4. 核心应用三重写行为方法 # 既保留父类的“日常业务”输出又追加经理特有的“管理”行为 super().work() print(f经理 {self.name} 正在主持部门周会。)运行与观测验证# 实例化子类对象 mgr Manager(高诚, 15000, 5000) # 验证 get_salary() 的重写 print(f经理实际薪资{mgr.get_salary()} 元) # 验证 work() 的重写 mgr.work()3. 方法重写注意事项在复杂的工程项目开发中使用方法重写必须遵守以下规范命名一致性重写方法的名称必须与父类方法完全一致。如果由于拼写错误如将 get_salary 错写为 get_saleryPython 解释器会将其识别为一个全新的子类专有方法导致重写机制失效super() 的参数传递在子类 __init__ 中调用 super().__init__(...) 时传递的参数必须严格匹配父类构造方法所要求的形参列表调用顺序通常情况下应当在子类重写方法的第一行代码中优先调用 super() 方法确保父类的基础状态或依赖环境初始化完毕后再执行子类的特有逻辑通过方法重写与 super() 的配合我们实现了子类对父类行为的精准重塑七、继承关系判断Python 提供了两个内置函数isinstance() 与 issubclass()。它们用于在程序运行期动态检测对象与类、以及类与类之间的血缘关系1. isinstance() 函数isinstance(object, classinfo) 用于检测一个对象实例是否属于指定的类或者属于该类的任何派生子类参数说明object需要检测的对象实例。classinfo目标类对象。也可以是一个包含多个类的元组Tuple只要对象属于元组中任意一个类就会返回 True返回值布尔值class Vehicle: pass class ElectricCar(Vehicle): pass class Airplane: pass # 实例化子类对象 tesla ElectricCar() # 1. 验证对象是否属于其直接类 print(isinstance(tesla, ElectricCar)) # 输出True # 2. 验证对象是否属于其父类关键特性 print(isinstance(tesla, Vehicle)) # 输出True # 3. 验证对象是否属于无关类 print(isinstance(tesla, Airplane)) # 输出False # 4. 使用类元组进行多向联合判定 print(isinstance(tesla, (Airplane, Vehicle))) # 输出True命中 Vehicle2. issubclass() 函数与 isinstance() 关注对象与类的关系不同issubclass(sub, classinfo) 专门用于检测类与类之间是否存在继承派生关系参数说明sub待检测的子类对象classinfo待检测的父类对象同样支持类元组返回值布尔值# 检测子类与父类的继承关系 print(issubclass(ElectricCar, Vehicle)) # 输出True print(issubclass(ElectricCar, Airplane)) # 输出False # 严格判定Python 中一个类默认被视为其自身的子类 print(issubclass(Vehicle, Vehicle)) # 输出True3. 为什么不能使用 type() 进行类型判断在基础语法学习中type(object) 常用于获取对象的类型。然而在面向对象的工程实践中强烈禁止使用 type(obj) Class 的形式进行动态类型检查以下代码清晰地展示了这种做法的设计缺陷# 错误示范使用 type() 判定继承链 if type(tesla) Vehicle: print(这是一辆交通工具) else: print(类型不匹配) # 实际运行输出类型不匹配底层原因分析type() 函数的行为是绝对精确的它只会返回对象在内存中被实例化时的那个最直接的类即 ElectricCar。它无法识别面向对象中的继承层次结构由于 tesla 确实是一辆交通工具Vehicle使用 type() 会直接割裂子类与父类之间的关联。因此遵从面向对象的设计规范应当统一使用 isinstance() 来实现鲁棒的类型检查。通过运行时类型检查我们能够确保继承链条中的动态对象在传递时的类型安全八、多重继承在前面的章节中我们探讨的都是一个子类只继承一个父类的单重继承结构。然而现实世界中的某些实体可能同时具备多种不同特征。为了模拟这种复杂的映射关系Python 支持多重继承即允许一个子类同时继承多个父类多重继承在带来强大扩展能力的同时也引入了方法命名冲突的隐患。为了彻底解决这一冲突Python 底层设计了一套严密的方法搜索算法1. 多重继承的语法实现在 Python 中实现多重继承只需在子类声明的小括号内将多个父类用英文逗号,隔开即可class Flyable: def fly(self): print(具备飞行能力。) class Swimmable: def swim(self): print(具备游泳能力。) # AmphibiousVehicle水陆两栖车同时继承两个父类 class AmphibiousVehicle(Flyable, Swimmable): pass obj AmphibiousVehicle() obj.fly() # 来自 Flyable obj.swim() # 来自 Swimmable2. 菱形继承与命名冲突当多个父类中存在同名方法时多重继承的复杂性会剧增。最经典的架构冲突被称为菱形继承类 A 是基类类 B 和类 C 分别继承自类 A而类 D 同时继承自类 B 和类 C如果类 B 和类 C 都重写了类 A 的某个同名方法那么当类 D 的实例调用该方法时Python 究竟应该执行类 B 的逻辑还是类 C 的逻辑为了消除这种歧义性Python 引入了MRO机制3. MRO 与 C3 线性化算法MRO 决定了一个类在继承拓扑结构中方法被搜索的先后物理顺序。Python 3 采用C3 线性化算法来计算这个顺序。该算法保证了多重继承环境下的三个原则子类优先于父类被检索在小括号中靠前的父类优先于靠后的父类被检索从左至右如果存在共同的终极基类该基类将最后被检索单调性如何查看 MRO 顺序Python 提供了两种内置途径来直接观测某个类的 MRO 线性列表读取类的魔术属性类名.__mro__返回元组调用类方法类名.mro()返回列表下面通过一个标准的菱形继承案例进行验证class A: def show(self): print(A.show) class B(A): def show(self): print(B.show) class C(A): def show(self): print(C.show) class D(B, C): pass # 查看类 D 的方法分辨率顺序 (MRO) print(D.mro()) # 输出[class D, class B, class C, class A, class object]搜索路径解析当执行d D() d.show()时解释器会严格按照 D.mro() 输出的线性链条依次寻找 show 方法首先检查 D 自身未找到接着检查左侧第一个父类 B成功命中立刻执行 B.show 并停止向下搜索因此屏幕最终输出 B.show。4. 多重继承下的 super 行为在单重继承中常将 super() 简单理解为调用父类。但在多重继承架构下这种理解会导致严重的逻辑错误super() 的本质并不是调用直系父类而是严格按照当前实例所属类的 MRO 列表调用该列表中的下一个Next类class Base: def __init__(self): print(Base 入口) print(Base 出口) class SubLeft(Base): def __init__(self): print(SubLeft 入口) super().__init__() # 注意此时它调用的可能不是 Base print(SubLeft 出口) class SubRight(Base): def __init__(self): print(SubRight 入口) super().__init__() print(SubRight 出口) class Final(SubLeft, SubRight): def __init__(self): print(Final 入口) super().__init__() print(Final 出口)执行 f Final() 的输出结果运行轨迹拆解首先Final.mro() 的物理顺序为[Final, SubLeft, SubRight, Base, object]进入 Final 的构造函数打印 Final 入口执行 super().__init__()在 MRO 链条中 Final 的下一个是 SubLeft于是进入 SubLeft打印 SubLeft 入口关键点在 SubLeft 中执行 super().__init__() 时Python 会参考原始实例的 MRO 链条即 Final 的 MRO。在链条中SubLeft 的下一个类是 SubRight而不是它的直系父类 Base于是控制权交给了 SubRight打印 SubRight 入口SubRight 执行 super().__init__()MRO 下一个是 Base打印 Base 入口、Base 出口各层函数开始执行未完成的尾部代码依次逆向弹栈最终打印出完整的拓扑嵌套闭合这种设计确保了在复杂的网状继承结构中每一个基类的构造方法有且仅被精确执行一次避免了资源的重复初始化5. 多重继承注意事项多重继承虽然强大但极易破坏软件代码的直观性。在软件工程中应遵循以下设计规范优先采用 Mixin 模式设计不要建立过于深厚的网状多重继承。通常推荐使一个子类主继承一个核心父类其余伴随的父类仅作为功能补充的 混入类Mixin。Mixin 类应当职能单一且其内部不定义 __init__ 状态属性凡重写必用 super()如果在多重继承的体系中某个子类重写了方法却直接通过父类名.方法名(self)进行调用将会彻底割裂 C3 算法的链条导致某些公共基类被重复执行保持参数一致性整个继承网路中相关联的同名方法的参数签名应当尽量保持兼容否则在 MRO 链条动态流转时极易触发参数投递不匹配的运行时崩溃总结本章围绕 Python 面向对象编程的基础知识展开系统学习了类、对象、实例化、属性、方法以及继承等核心内容。通过本章的学习我们不仅掌握了如何定义类、创建对象还了解了实例属性与类属性、实例方法、类方法、静态方法之间的区别以及继承、多重继承和方法重写等重要特性与面向过程相比面向对象更加注重对现实世界的抽象它将数据与操作数据的方法封装在一起使程序具有更好的可维护性、可扩展性和复用性。而继承机制则进一步减少了重复代码让不同类之间能够共享已有的功能从而提高开发效率不过本章更多介绍的是如何使用面向对象而真正理解面向对象的设计思想还需要进一步学习对象之间是如何协作的以及 Python 在面向对象方面提供的更多高级特性下一篇文章我们将继续深入学习 Python 面向对象编程重点介绍封装权限控制、多态、抽象类、鸭子类型以及对象的内存模型等内容从 会使用类 逐步迈向 理解面向对象设计真正掌握 Python 面向对象编程的核心思想