Python面向对象编程-1
本章学习知识点
- 类与对象:class 定义类、init 构造方法、实例属性与类属性
- 封装:私有属性(__xxx)、公有方法(对外提供接口)
- 继承:单继承、多继承(MRO 顺序)、方法重写
面向对象编程(Object-Oriented Programming,简称 OOP)是一种以「对象」为核心的编程思想,它将数据(属性)和操作数据的方法封装在一起,通过封装、继承、多态三大核心特性实现代码复用和逻辑解耦,是 Python 开发中不可或缺的核心技术。
一、类与对象
类(Class)是对一类事物相似特征与行为的抽象总结(如"教师类"包含姓名、授课技能等共性),而对象是类的具体实例(如"张老师""李老师")。类比现实世界:类是"汽车设计图纸",对象是"根据图纸造出的具体汽车"------类是模板,对象是模板创建的实体。
1.1 类的定义与核心规则
- 类通过
class关键字定义,类体包含属性(数据)和方法(函数),其核心规则决定了类的使用逻辑:- 定义顺序:必须先定义类,后创建对象(与函数"先定义后调用"逻辑一致);
- 执行特性:类体代码在「类定义阶段」立即执行,生成类的名称空间(存放类的所有属性和方法);
- 调用差异:调用函数返回执行结果,调用类(实例化)返回对象本身。
- 关键操作:
类.__dict__:查看类的名称空间(包含所有类属性和方法);- 点语法(.):Python 专属的属性访问方式,如
类.属性或对象.属性。
1.2、self与__init__
1.2.1、说明
-
self:类实例的 "自身引用"
self是 Python 类中实例方法的必选第一个参数 (约定俗成的命名,非关键字),核心作用是指代当前调用方法的实例本身。- 谁调用方法,
self就指向谁; - 通过
self可以访问 / 修改实例的专属属性、调用实例的其他方法; - 调用实例方法时,Python 会自动把实例传给
self,无需手动传参。
- 谁调用方法,
-
init:实例的 "初始化方法"
__init__是 Python 类的特殊内置方法(也叫构造方法),核心作用是:- 创建实例时自动触发执行,无需手动调用;
- 用于给实例绑定初始属性,完成实例的初始化;
__init__必须以self为第一个参数,支持自定义参数,且不能返回非None的值。
-
两者间关系
__init__是 "给实例装初始属性" 的流程,self是这个流程中 "被装修的那个实例"- 通过
self在__init__中绑定的属性,会成为每个实例的专属数据。
1.2.2、示例
-
示例
pythonclass Student: # 类属性:所有实例共享(比如学校名称) school = "北京大学" # __init__:初始化方法,创建实例时自动执行 # self:指向当前创建的Student实例(必选第一个参数) # name/age/score:自定义参数,用于给实例绑定专属属性 def __init__(self, name, age, score): # 给当前实例绑定专属属性(self.属性名 = 值) self.name = name # 姓名:每个学生独有 self.age = age # 年龄:每个学生独有 self.score = score # 分数:每个学生独有 # 初始化时通过self调用实例方法(校验分数合法性) self.check_score() # 实例方法:检查分数是否合法(依赖self访问实例属性) def check_score(self): if not 0 <= self.score <= 100: raise ValueError(f"{self.name}的分数{self.score}不合法!必须在0-100之间") # 实例方法:打印学生信息(通过self访问实例属性/类属性) def show_info(self): print(f"【学校】:{self.school}(类属性,所有学生共享)") print(f"【姓名】:{self.name},【年龄】:{self.age},【分数】:{self.score}(实例属性,专属)") # ---------------------- 测试实例 ---------------------- # 1. 创建第一个学生实例:自动调用__init__,Python自动把s1传给self s1 = Student("张三", 18, 95) # 调用实例方法:self指向s1,访问s1的属性 s1.show_info() # 输出: # 【学校】:北京大学(类属性,所有学生共享) # 【姓名】:张三,【年龄】:18,【分数】:95(实例属性,专属) print("-" * 50) # 2. 创建第二个学生实例:self此时指向s2,绑定s2的专属属性 s2 = Student("李四", 19, 88) s2.show_info() # 输出: # 【学校】:北京大学(类属性,所有学生共享) # 【姓名】:李四,【年龄】:19,【分数】:88(实例属性,专属) # 3. 错误示例:分数不合法(__init__中调用check_score触发报错) # s3 = Student("王五", 20, 105) # 报错:王五的分数105不合法!必须在0-100之间 # 查看对象的名称空间(独有属性) 通过这个属性可以看到类中所有健值对 print(t1.__dict__)- 忘记 self 参数 :
__init__和所有实例方法的第一个参数必须是self,否则创建实例 / 调用方法时会报错(Python 自动传实例,但方法没接收); - 属性绑错位置 :
__init__中若直接写name = name(少了 self),会变成局部变量,外部无法通过实例访问; - 手动调用__init__ :无需手动执行
s1.__init__(),创建实例时解释器会自动调用,重复调用可能覆盖已有属性; - self 不是关键字 :可以用
this等名字代替,但违反 Python 社区规范,会降低代码可读性; - __init__无返回值 :
__init__只能返回None,强行返回其他值会报错。
- 忘记 self 参数 :
-
总结
self:是实例的 "自身引用",区分不同实例的专属属性 / 方法,谁调用就指向谁;__init__:是实例的 "初始化入口",创建实例时自动执行,通过self给实例绑定初始属性;- 核心逻辑:创建实例 → Python 自动传实例给
self→__init__执行 → 通过self完成实例属性初始化。
-
一句话记住:
__init__负责给实例 "赋初始值",self负责定位 "该给哪个实例赋值",两者配合让每个实例都有自己的初始状态。
1.3、属性查找规则与绑定特性
- 说明
- "属性查找规则" 是指访问实例 / 类的属性时,解释器从哪里找这个属性;
- "绑定特性" 是指属性(实例属性 / 类属性 / 方法)如何关联到实例或类。
1.3.1、查找规则
-
先明确:属性的两类核心绑定形式
属性绑定的核心是 "归属"------ 属性到底属于类 还是实例,这决定了它的共享性和查找逻辑:
-
类属性:绑定到类,所有实例共享
- 定义方式:直接在类体中(__init__外)赋值;
- 绑定对象:类本身,所有基于该类创建的实例都能访问;
- 修改方式 :只能通过
类名.类属性修改,实例修改会创建同名实例属性(遮蔽类属性)。
-
实例属性:绑定到实例,每个实例独有
- 定义方式 :在__init__(或其他实例方法)中通过
self.属性名 = 值赋值; - 绑定对象:具体的实例,不同实例的同名属性相互独立;
- 修改方式 :通过
实例名.属性名或self.属性名修改,仅影响当前实例。
- 定义方式 :在__init__(或其他实例方法)中通过
-
示例
python# 绑定特性示例 class Student: # 类属性:绑定到Student类,所有实例共享 school = "北京大学" def __init__(self, name): # 实例属性:绑定到当前实例(self),每个实例独有 self.name = name # 1. 类属性:类和实例都能访问(共享) print(Student.school) # 输出:北京大学 s1 = Student("张三") s2 = Student("李四") print(s1.school, s2.school) # 输出:北京大学 北京大学 # 2. 实例属性:仅实例能访问,类无法访问 print(s1.name) # 输出:张三 # print(Student.name) # 报错:类没有name属性 # 3. 实例"修改"类属性:实际是创建同名实例属性(遮蔽) s1.school = "清华大学" print(s1.school) # 输出:清华大学(实例属性遮蔽类属性) print(s2.school) # 输出:北京大学(类属性未变) print(Student.school) # 输出:北京大学(类属性未变) # 4. 真正修改类属性:通过类名修改(所有实例共享变化) Student.school = "复旦大学" print(s2.school) # 输出:复旦大学(类属性变化,s2无同名实例属性,读取新值) print(s1.school) # 输出:清华大学(s1有同名实例属性,仍读自己的)
-
-
属性查找规则:"先实例,后类,再父类"
当通过
实例.属性名访问属性时,Python 解释器会按以下优先级顺序 查找,找到即停止;若全找不到则报AttributeError,核心口诀 :实例找属性,先找自己的,再找类的;类找属性,只找自己的和父类的。-
原理说明
- 第一步 :查找当前实例的 "实例属性字典"(
__dict__),看是否有该属性; - 第二步:若实例没有,查找实例所属类的 "类属性字典",看是否有该属性;
- 第三步:若类没有,按继承关系向上查找父类的类属性(多继承有 MRO 规则,此处简化);
- 第四步 :若都找不到,触发
AttributeError。
- 第一步 :查找当前实例的 "实例属性字典"(
-
示例
pythonclass cls: default = 0 def __init__(self, name): self.name = name class Person(cls): species="人类" def __init__(self, name): self.name = name p = Person('张三') # 查找p.name:第一步找到实例属性,返回"张三" print(p.name) # 查找p.species:实例无该属性,第二步找到类属性,返回"人类" print(p.species) # 查找p.species:实例无该属性,第二步找到类属性,第三步找父类属性. 返回 "0" print(p.default) # 查找p.age:实例和类都没有,报错 # print(p.age) # AttributeError: 'Person' object has no attribute 'age' -
查找规则的关键细节
-
实例属性会 "遮蔽" 类属性
- 如果实例有和类同名的属性,查找时会优先返回实例属性(类属性被 "隐藏"),如前面
Student示例中s1.school。
- 如果实例有和类同名的属性,查找时会优先返回实例属性(类属性被 "隐藏"),如前面
-
类无法访问实例属性
- 通过
类名.属性名查找时,只会找类属性 / 父类属性,不会找实例属性(因为实例属性属于具体实例,不属于类)。
- 通过
-
动态绑定属性
-
Python 支持 "运行时动态绑定属性"------ 即使不在__init__中定义,也能给实例 / 类新增属性:
pythonclass Cat: pass # 动态给类绑定属性 Cat.color = "黑色" # 动态给实例绑定属性 c = Cat() c.name = "小黑" print(Cat.color) # 输出:黑色 print(c.name) # 输出:小黑 print(c.color) # 输出:黑色(实例找color,先找自己没有,再找类的)
-
-
-
1.3.2、方法的绑定特性
方法本质是 "可调用的属性",绑定规则和普通属性略有不同:
-
实例方法(带 self):绑定到实例
- 定义:
def 方法名(self, ...):; - 调用:
实例.方法名()→ Python 自动把实例传给 self; - 特性:必须通过实例调用(或手动传类实例给 self),依赖实例的属性。
- 定义:
-
类方法(带 cls):绑定到类
- 定义:
@classmethod装饰 +def 方法名(cls, ...):; - 调用:
类名.方法名()或实例.方法名()→ Python 自动把类传给 cls; - 特性:操作类属性,不依赖实例。
- 定义:
-
静态方法:无绑定(独立函数)
- 定义:
@staticmethod装饰 +def 方法名(...):(无 self/cls); - 调用:
类名.方法名()或实例.方法名(); - 特性:和类 / 实例无关,仅逻辑上归属类。
- 定义:
-
示例
pythonclass Dog: type = "犬科" # 类属性 # 实例方法:绑定到实例,依赖self(实例) def __init__(self, name): self.name = name # 实例属性 # 实例方法:绑定到实例 def bark(self): print(f"{self.name}:汪汪叫") # 类方法:绑定到类,依赖cls(类) @classmethod def change_type(cls, new_type): cls.type = new_type # 静态方法:无绑定 @staticmethod def run(): print("狗狗在跑") # 实例方法:绑定实例,self指向d1 d1 = Dog("旺财") d1.bark() # 输出:旺财:汪汪叫 # 类方法:绑定类,cls指向Dog Dog.change_type("哺乳动物") print(Dog.type) # 输出:哺乳动物 d1.change_type("食肉动物") # 实例调用类方法,cls仍指向Dog print(Dog.type) # 输出:食肉动物 # 静态方法:无绑定,类/实例调用都一样 Dog.run() # 输出:狗狗在跑 d1.run() # 输出:狗狗在跑
核心总结
-
绑定特性
- 类属性:绑定到类,所有实例共享,修改需通过类名;
- 实例属性 :绑定到实例,每个实例独有,通过 self/init/ 动态赋值创建;
- 实例方法:绑定到实例,依赖 self;类方法绑定到类(@classmethod),依赖 cls;静态方法(@staticmethod)无绑定。
-
查找规则
- 实例访问属性:
实例.__dict__→ 类.dict → 父类.dict; - 类访问属性:类.dict → 父类.dict;
- 实例属性会遮蔽同名类属性,动态绑定的属性也遵循此规则。
- 实例访问属性:
二、OOP 三大核心特性
-
封装、继承、多态是面向对象编程的核心支柱,三者形成 "基础 - 复用 - 扩展" 的闭环:
-
封装构建模块化的代码单元,
-
继承实现代码复用与分层设计,
-
多态则在统一接口下支持灵活的功能扩展,最终达成 "高内聚、低耦合" 的代码设计目标。
特性 核心目标 核心价值 落地关键 封装 隐藏内部实现,暴露安全接口 保护数据安全、降低外部依赖、简化使用 私有化成员、getter/setter、接口收敛 继承 复用已有逻辑,扩展新功能 减少重复代码、构建类的层级关系 父类提取共性、子类派生扩展、super () 调用 多态 统一接口,不同实现 解耦调用与实现、提升代码扩展性 父类定义接口、子类重写方法、抽象基类约束
-
2.1、封装
封装是将对象的属性和方法包裹在类的边界内,对外隐藏复杂的内部实现,仅提供经过设计的公开接口供外部访问。核心是 "对内开放、对外约束",既保证数据安全,又降低外部使用的复杂度。
- 封装的核心逻辑
- 隐藏(封):通过语法规则限制外部对内部成员的直接访问,避免非法操作;
- 整合(装):将属性(数据)和方法(行为)封装为统一的逻辑单元,形成独立的名称空间;
- 接口化(用):对外暴露简洁、稳定的接口,内部实现可自由修改而不影响外部调用。
2.1.1、__私有成员
私有成员:实现核心逻辑隐藏
-
Python 通过 "双下划线前缀(__)" 实现成员私有化(本质是名称修饰,非严格私有),私有成员仅能在类内部访问,外部需通过公开接口操作。
pythonclass User: def __init__(self, username, password): self.username = username # 公有属性:对外可见 self.__password = password # 私有属性:仅类内部访问 # 私有方法:封装加密逻辑(外部无需感知) def __encrypt_pwd(self, pwd): return pwd + "_salt" # 模拟加盐加密 # 公开接口:验证密码(封装核心逻辑,对外提供安全操作) def verify_password(self, input_pwd): encrypted_input = self.__encrypt_pwd(input_pwd) return encrypted_input == self.__encrypt_pwd(self.__password) # 实例化与调用 user = User("zhangsan", "123456") print(user.username) # 公有属性可直接访问:zhangsan print(user.verify_password("123456")) # 通过接口验证:True # print(user.__password) # 直接访问私有属性:报错(AttributeError) # print(user.__encrypt_pwd("123")) # 直接调用私有方法:报错(AttributeError) # 【原理说明】名称修饰规则:__xxx → _类名__xxx(不推荐外部使用,破坏封装) print(user._User__password) # 强制访问私有属性(仅作原理说明,禁止实际使用) -
属性访问器:安全的属性操作接口
-
对于需要外部访问的私有属性,推荐使用
@property(getter)和@属性名.setter(setter)封装,在方法中添加数据校验、格式转换等逻辑,保证数据合法性。pythonclass Student: def __init__(self, name, age): self.name = name self.__age = age # 私有年龄属性(需校验范围) # getter:读取属性(无需加(),像访问普通属性一样使用) @property def age(self): return self.__age # setter:修改属性(直接赋值,自动触发校验) @age.setter def age(self, new_age): if not isinstance(new_age, int): raise TypeError("年龄必须是整数!") if 0 <= new_age <= 120: self.__age = new_age else: raise ValueError("年龄必须在0-120之间!") # 调用示例 stu = Student("小明", 18) print(stu.age) # 读取:18 stu.age = 20 # 修改:合法值 print(stu.age) # 输出:20 # stu.age = 150 # 非法值:触发ValueError # stu.age = "20" # 非法类型:触发TypeError
-
-
封装设计原则
- 最小暴露原则:仅暴露必要的接口,非核心逻辑全部私有化;
- 接口稳定原则:公开接口的命名和参数尽量稳定,内部实现可灵活调整;
- 数据校验原则:所有外部传入的数据,必须在接口中完成合法性校验。
2.2、继承与派生
继承是让一个类(子类 / 派生类)复用另一个类(父类 / 基类)的属性和方法,子类可在父类基础上新增属性、重写方法,实现 "共性抽取、个性扩展"。Python 支持单继承和多继承,推荐以单继承为主,多继承仅用于整合独立功能。
- 核心概念
- 父类 / 基类 :提供共性功能的类(如 "动物类");
- 查看父类 :通过
类名.__bases__查看子类的父类列表。 - 继承语法 :
class 子类名(父类1, 父类2, ...):(Python 支持多继承); - 新式类与经典类 :Python3 中所有类默认继承
object(新式类),采用"广度优先"查找规则;Python2 中未继承object的是经典类,采用"深度优先"(实际开发均用新式类);
- 查看父类 :通过
- 子类 / 派生类:继承并扩展父类的类(如 "狗类""猫类");
- 派生:子类新增属性 / 方法、重写父类方法的过程;
- MRO(方法解析顺序):多继承时,Python 查找方法的优先级规则(C3 线性化算法)。
- 父类 / 基类 :提供共性功能的类(如 "动物类");
2.2.1、单继承
-
单继承:基础复用与扩展
-
单继承是最易维护的继承方式,适合构建 "通用→特殊" 的层级关系。
python# 父类:抽取所有动物的共性 class Animal: def __init__(self, name, color): self.name = name self.color = color def eat(self): print(f"{self.color}的{self.name}正在进食") # 子类:狗类(继承+派生) class Dog(Animal): def __init__(self, name, color, breed): # 复用父类初始化逻辑(推荐super(),适配继承体系) super().__init__(name, color) self.breed = breed # 新增子类独有属性:品种 def bark(self): print(f"{self.breed} {self.name}在汪汪叫") # 新增子类独有方法 def eat(self): # 重写父类方法(保留父类逻辑+扩展) super().eat() print(f"{self.name}专吃骨头") # 子类个性化逻辑 # 调用示例 dog = Dog("旺财", "黄色", "中华田园犬") dog.eat() # 输出:黄色的旺财正在进食 → 旺财专吃骨头 dog.bark() # 输出:中华田园犬 旺财在汪汪叫
-
-
子类调用父类方法的两种方式
方式 语法 适用场景 优点 缺点 super() super().方法名(参数)单继承 / 多继承 适配 MRO 顺序,无需硬编码父类名 依赖继承顺序 直接调用 父类名.方法名(self, 参数)多继承精准调用 不依赖继承顺序,精准指定父类 硬编码父类名,耦合度高 -
通过「父类名」直接调用(通用,支持多继承)
pythonclass Parent: def __init__(self): print("父类初始化方法") class Child(Parent): def __init__(self): # 第一步:调用父类的__init__(手动传self) Parent.__init__(self) # 第二步:执行子类自己的初始化逻辑 print("子类初始化方法") c1 = Child() # 输出顺序: # 父类初始化方法 # 子类初始化方法 -
通过
super()调用(推荐,适配 MRO 顺序)pythonclass Parent: def __init__(self): print("父类初始化方法") class Child(Parent): def __init__(self): # 第一步:调用父类的__init__(无需传self,super自动处理) super().__init__() # 第二步:执行子类自己的初始化逻辑 print("子类初始化方法") c1 = Child() # 输出和方式1一致: # 父类初始化方法 # 子类初始化方法 -
如果两种都不写 <-- 异常说明
pythonclass Parent: def __init__(self): print("父类初始化方法") class Child(Parent): def __init__(self): print("子类初始化方法") c1 = Child() c1.Parent()-
c1是Child的实例,它的属性只有Child类中通过self.xxx绑定的内容,没有Parent这个属性 -
若强行想通过实例访问父类,正确的方式是先通过
type(c1).__bases__拿到父类,再调用,但实际开发中绝对不推荐pythonc1 = Child() # 错误写法 # c1.Parent.__init__() # 直接报错:'Child' object has no attribute 'Parent' # 强行通过实例找父类并调用(仅作原理演示,禁止实际使用) ParentClass = type(c1).__bases__[0] # 拿到Child的第一个父类(Parent) ParentClass.__init__(c1) # 手动传实例c1,调用父类__init__ # 输出:父类初始化方法
-
-
两种加上函数
pythonclass Par: def __init__(self,name): self.name = name def show(self): print("父类属性:",self.name) class Child(Par): def __init__(self, name): # 复用父类初始化逻辑 super().__init__(name) def show(self): # super()调用(推荐) Par.show(self) print("子类属性:",self.name) c1=Child("子类") c1.show() # 父类属性: 子类 # 子类属性: 子类 -
总结
写法 是否正确 适用场景 Parent.__init__(self)✅ 正确 单 / 多继承,需精准调用指定父类 super().__init__()✅ 正确(推荐) 单继承,或按 MRO 顺序调用父类 c1.Parent.__init__()❌ 错误 无适用场景,实例无 Parent 属性 type(c1).__bases__[0].__init__(c1)✅ 语法正确但不推荐 仅原理演示,实际开发禁止使用
-
2.2.3、多继承与 MRO 顺序
多继承允许子类整合多个父类的功能,但易引发方法冲突,需通过 MRO 规则解决,采用 C3 线性化算法。
-
多继承基础示例
python# 父类1:飞行能力 class Flyable: def fly(self): print("拥有飞行能力!") # 父类2:游泳能力 class Swimmable: def swim(self): print("拥有游泳能力!") # 子类:鸭子类(继承飞行+游泳能力) class Duck(Flyable, Swimmable): def __init__(self, name): self.name = name # 测试 # 查看MRO顺序(子类→父类1→父类2→object) print(Duck.mro()) # 输出:[<class '__main__.Duck'>, <class '__main__.Flyable'>, <class '__main__.Swimmable'>, <class 'object'>] duck = Duck("小黄鸭") duck.fly() # 输出:拥有飞行能力! duck.swim() # 输出:拥有游泳能力! -
MRO 核心规则与查看方式
-
核心规则:子类优先于父类、先继承的父类优先于后继承的父类(优先级1,优先级2)、每个类仅被查找一次;
-
查看 MRO:通过
类名.__mro__或类名.mro()查看,返回的元组/列表即为方法查找顺序。 -
示例:MRO 解决同名方法冲突
python# 父类1:A class A: def show(self): print("A类的show方法") # 父类2:B(继承A) class B(A): def show(self): print("B类的show方法") # 父类3:C(继承A) class C(A): def show(self): print("C类的show方法") # 子类:D(继承B、C) class D(B, C): pass # 查看MRO顺序 print(D.mro()) # 输出:[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] # 调用show方法(按MRO顺序查找,优先B类) d = D() d.show() # 输出:B类的show方法 # 子类重写后,优先调用自身 class D(B, C): def show(self): print("D类的show方法") d2 = D() d2.show() # 输出:D类的show方法
-
-
菱形继承与多继承建议
- 菱形继承:子类继承多个父类,且多个父类最终继承同一个基类(如所有类最终继承
object),此时 MRO 顺序尤为重要。 - 多继承使用建议:
- 尽量避免「多层多继承」,复杂度高且易出现逻辑冲突;
- 若需整合多个独立功能(如飞行+游泳),优先使用「组合」而非多继承;
- 多继承场景下,务必通过
mro()确认方法查找顺序,避免同名方法冲突。
- 菱形继承:子类继承多个父类,且多个父类最终继承同一个基类(如所有类最终继承
2.3 多态
多态是指 "不同子类继承同一父类后,重写父类同名方法,使得同一接口调用能适配不同子类的实现"。核心是 "接口统一,实现各异",大幅提升代码的可扩展性。
-
多态的核心要素
- 存在继承关系(子类继承同一父类);
- 子类重写父类的同名方法;
- 外部通过父类接口调用,无需区分具体子类类型
-
多态实战案例:统一接口实现不同功能
-
以"计算不同形状的面积"为例,通过多态实现"统一调用接口,自动适配不同形状"。
python# 父类:形状类(定义统一接口) class Shape: # 统一接口:计算面积(子类必须重写,否则报错) def calculate_area(self): raise NotImplementedError("子类必须重写calculate_area方法!") # 子类1:圆形(重写面积计算方法) class Circle(Shape): def __init__(self, radius): self.radius = radius # 圆形独有属性:半径 def calculate_area(self): import math return math.pi * self.radius ** 2 # 圆形面积公式 # 子类2:矩形(重写面积计算方法) class Rectangle(Shape): def __init__(self, length, width): self.length = length # 矩形独有属性:长 self.width = width # 矩形独有属性:宽 def calculate_area(self): return self.length * self.width # 矩形面积公式 # 子类3:三角形(重写面积计算方法) class Triangle(Shape): def __init__(self, base, height): self.base = base # 三角形独有属性:底 self.height = height # 三角形独有属性:高 def calculate_area(self): return 0.5 * self.base * self.height # 三角形面积公式 # 统一调用函数(多态核心:无需区分对象类型,调用统一接口) def print_area(shape): if isinstance(shape, Shape): print(f"{shape.__class__.__name__}的面积为:{shape.calculate_area():.2f}") else: print("传入的不是Shape子类对象!") # 测试多态性 circle = Circle(5) rectangle = Rectangle(4, 6) triangle = Triangle(3, 8) print_area(circle) # 输出:Circle的面积为:78.54 print_area(rectangle) # 输出:Rectangle的面积为:24.00 print_area(triangle) # 输出:Triangle的面积为:12.00 # 扩展:新增正方形类(无需修改print_area函数) class Square(Shape): def __init__(self, side): self.side = side def calculate_area(self): return self.side ** 2 square = Square(5) print_area(square) # 输出:Square的面积为:25.00
-
-
多态的标准化实现:抽象基类(ABC)
-
通过
abc模块定义抽象基类,可强制子类实现指定方法,保证接口统一(适合团队开发 / 框架设计)。pythonimport abc # 抽象基类:定义文件读取的统一标准(无法实例化) class FileReader(metaclass=abc.ABCMeta): @abc.abstractmethod # 抽象方法,子类必须实现 def read(self): pass # 子类1:文本文件读取器 class TxtReader(FileReader): def __init__(self, file_path): self.file_path = file_path def read(self): print(f"读取文本文件:{self.file_path},格式:UTF-8") # 子类2:Excel文件读取器 class ExcelReader(FileReader): def __init__(self, file_path): self.file_path = file_path def read(self): print(f"读取Excel文件:{self.file_path},格式:xlsx") # 子类3:未实现抽象方法(实例化报错) # class PdfReader(FileReader): # pass # pdf = PdfReader("test.pdf") # TypeError: Can't instantiate abstract class PdfReader with abstract method read # 统一调用接口 def read_file(reader): if isinstance(reader, FileReader): reader.read() # 测试 txt_reader = TxtReader("data.txt") excel_reader = ExcelReader("data.xlsx") read_file(txt_reader) # 输出:读取文本文件:data.txt,格式:UTF-8 read_file(excel_reader) # 输出:读取Excel文件:data.xlsx,格式:xlsx
-
-
多态设计价值
- 解耦:调用者只需关注接口,无需关注具体实现;
- 扩展:新增功能仅需新增子类,无需修改原有调用逻辑(开闭原则);
- 统一:通过抽象基类强制接口规范,降低团队协作成本。
2.4、三大特性整合总结
| 特性 | 核心思想 | 典型应用场景 | 避坑要点 |
|---|---|---|---|
| 封装 | 隐藏细节,暴露接口 | 数据校验、权限控制、复杂逻辑封装 | 避免过度封装,接口不宜过多 |
| 继承 | 抽取共性,扩展个性 | 类的层级设计、基础功能复用 | 避免多层多继承,优先 super () 调用 |
| 多态 | 接口统一,实现各异 | 通用功能适配、插件化扩展 | 依赖抽象接口,而非具体实现 |
-
三者协同设计示例:
python# 1. 基类:定义统一接口(所有用户的共性) class User: def __init__(self, name): self.name = name # 公有属性:用户名 # 统一接口:operate(子类必须重写,体现多态核心) def operate(self): raise NotImplementedError("子类必须实现operate方法") # 2. 子类1:管理员(重写operate方法) class Admin(User): def operate(self): print(f"{self.name}:执行管理员操作") # 3. 子类2:普通用户(重写operate方法) class NormalUser(User): def operate(self): print(f"{self.name}:执行普通用户操作") # 4. 多态核心:统一调用函数(面向父类接口) def execute_operation(user: User): user.operate() # 5. 调用示例 execute_operation(Admin("管理员")) # 输出:管理员:执行管理员操作 execute_operation(NormalUser("用户")) # 输出:用户:执行普通用户操作 ######################### 也可以写成, 但需要人工判断是否在这个接口中 def prit_user_role(user, User): if isinstance(user, User): user.operate() else: print("未被定义的用户角色") a1=Admin('admin') a2=NormalUser('normal') prit_user_role(a1, Admin) prit_user_role(a2, NormalUser)- 解析一下组合
def execute_operation(user: User):------ 类型注解user: User是 Python 的类型注解(Type Hint),作用是:- 提示开发者:这个函数的
user参数期望接收User类或其子类的实例; - 辅助 IDE(如 PyCharm)做语法检查,避免传错类型(但 Python 运行时不会强制校验,只是 "提示");
- 核心意义:明确函数的入参接口 ------ 只要是
User子类的实例,都能传入,体现 "统一接口" 的设计。
- 提示开发者:这个函数的
user.operate()------ 调用统一接口(多态的核心执行逻辑)- 这里的
user是函数接收的参数,它本质是User子类(Admin/NormalUser)的实例; - 调用
user.operate()时,Python 会根据user的实际类型,自动调用对应子类的operate方法:- 当传入
Admin("管理员")时,user是Admin实例,调用Admin.operate(); - 当传入
NormalUser("用户")时,user是NormalUser实例,调用NormalUser.operate();
- 当传入
- 这就是多态的核心:同一个接口(
operate()),不同实例表现出不同行为。
- 这里的
- 解析一下组合