Python面向对象编程-2
本章学习知识点
- OOP 进阶特性:组合、类方法、property装修器
- 对象自省机制:
- 类型判断:
isinstance/issubclass/type,检查对象的类型 / 继承关系 - 反射机制:
hasattr/getattr/setattr/delattr, 检查 / 操作对象的属性 / 方法,即反射
- 类型判断:
一、OOP 进阶特性
-
技术归类说明
概念 细分归类 核心定位 组合 代码复用机制 与继承并列的 "对象级复用" 方式 类方法 类的方法类型(绑定方法) 绑定到类的特殊方法 property装饰器属性管理机制 封装属性访问的进阶工具 -
补充
- 组合是实现 "复用" 的手段(对应继承特性的补充);
- 类方法是优化 "类层面逻辑封装" 的手段(对应封装特性);
property装饰器是强化 "属性封装" 的手段(对应封装特性)。
1.1、组合
组合是与继承并列的核心复用手段,核心逻辑是「将其他类的实例作为当前类的属性」,通过对象间的 "拥有关系" 拼装复杂功能。当类与类之间是 "has-a"(拥有)关系时,优先使用组合而非继承。
-
组合与继承的核心区别
类间关系 推荐方式 核心逻辑 典型示例 从属关系(is-a) 继承 子类是父类的一种具体类型 狗是动物、学生是人、管理员是用户 拥有关系(has-a) 组合 一个类的实例拥有另一个类的实例 老师拥有课程、汽车拥有发动机、手机拥有电池 -
组合实战示例
-
以"教师拥有课程"为例,通过组合实现教师与课程的关联,比继承更符合逻辑。
python# 独立功能类:课程(可被复用) class Course: def __init__(self, name, price, duration): self.name = name # 课程名称 self.price = price # 课程价格 self.duration = duration # 课程时长(月) def show_course(self): """展示课程信息""" return f"《{self.name}》- 价格:{self.price}元,时长:{self.duration}月" # 基础父类:人类(提取共性属性) class People: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex # 业务类:教师(继承+组合) class Teacher(People): def __init__(self, name, age, sex, level): # 继承:复用父类初始化逻辑 super().__init__(name, age, sex) self.level = level # 教师专属属性:等级 self.courses = [] # 组合核心:存放Course实例的列表(教师拥有课程) def add_course(self, course): """添加课程(校验类型,保证组合的合法性)""" if isinstance(course, Course): self.courses.append(course) print(f"✅ 教师{self.name}成功添加课程:{course.name}") else: print("❌ 请传入Course类的实例!") def show_teacher_info(self): """展示教师及所带课程信息""" print(f"\n【教师信息】") print(f"姓名:{self.name},年龄:{self.age},性别:{self.sex},等级:{self.level}") print("【所带课程】") if not self.courses: print(" 暂无授课课程") return for idx, course in enumerate(self.courses, 1): print(f" {idx}. {course.show_course()}") # 测试组合逻辑 if __name__ == "__main__": # 1. 创建独立的课程实例 python_course = Course("Python全栈开发", 2999, 6) java_course = Course("Java后端开发", 2699, 5) # 2. 创建教师实例(继承People),并组合课程 teacher_zhang = Teacher("张老师", 35, "男", "高级讲师") teacher_zhang.add_course(python_course) teacher_zhang.add_course(java_course) # 3. 展示信息(体现组合的价值) teacher_zhang.show_teacher_info()- 组合的设计优势
- 低耦合:课程类和教师类可独立修改(如课程价格规则变更,不影响教师类);
- 高灵活:可动态添加 / 删除课程,无需修改类结构;
- 易扩展:新增 "学生拥有课程" 场景时,可直接复用 Course 类,仅需新增 Student 类并组合。
- 组合的设计优势
-
1.2、类方法与对象方法
类中的方法按 "绑定对象" 可分为对象方法、类方法、静态方法,不同类型的方法适配不同的使用场景,正确选型能大幅提升代码的逻辑性和可读性。
-
方法类型核心特性对比
方法类型 装饰器 绑定对象 自动传入参数 核心作用 调用方式 对象方法 无 实例对象 self(当前实例) 操作实例属性、实现实例专属行为 实例.方法名 ()(推荐) 类.方法名 (实例) 类方法 @classmethod 类本身 cls(当前类) 操作类属性、实现工厂模式(创建实例)、批量修改类属性 类.方法名 ()(推荐) 实例。方法名 () 静态方法 @staticmethod 无 无 提供与类 / 实例相关的工具函数(无属性依赖) 类.方法名 () 实例.方法名 () -
示例
pythonclass Student: # 类属性:所有学生共享 school = "第一中学" def __init__(self, name, age): # 实例属性:每个学生独有 self.name = name self.age = age # 1. 对象方法:依赖实例属性 def show_student_info(self): """展示单个学生的信息(操作实例属性)""" print(f"姓名:{self.name},年龄:{self.age},学校:{self.school}") # 2. 类方法:依赖类属性/创建实例 @classmethod def update_school(cls, new_school): """修改学校名称(操作类属性)""" cls.school = new_school print(f"🏫 全校已统一修改为:{cls.school}") @classmethod def create_from_str(cls, info_str): """工厂模式:从字符串快速创建实例(类方法核心场景)""" try: name, age = info_str.split("-") return cls(name, int(age)) except Exception as e: print(f"❌ 解析失败:{e},请按'姓名-年龄'格式输入") return None # 3. 静态方法:无属性依赖的工具函数 @staticmethod def is_adult(age): """判断是否成年(通用工具逻辑)""" return age >= 18 # 测试不同方法 if __name__ == "__main__": # 1. 对象方法调用(必须先创建实例) stu1 = Student("小明", 17) stu1.show_student_info() # 输出:姓名:小明,年龄:17,学校:第一中学 # 2. 类方法调用(直接通过类调用) Student.update_school("第二中学") stu2 = Student.create_from_str("小红-19") if stu2: stu2.show_student_info() # 输出:姓名:小红,年龄:19,学校:第二中学 # 3. 静态方法调用(工具函数) print(f"小明是否成年:{Student.is_adult(stu1.age)}") # 输出:False print(f"小红是否成年:{stu2.is_adult(stu2.age)}") # 输出:True -
方法选型原则
-
优先选对象方法 :若方法需要访问 / 修改实例属性(如
name、age); -
选类方法 :若方法需要访问 / 修改类属性(如
school),或需要封装 "实例创建逻辑"; -
选静态方法:若方法是通用工具(如数据校验、格式转换),且不依赖类 / 实例的任何属性;
-
禁止滥用 :避免用静态方法实现本应属于对象 / 类方法的逻辑(如静态方法中强行操作
self/cls)。
-
1.3、property 装饰器
property装饰器是Python面向对象编程中用于属性管理的重要工具,核心作用是「将方法伪装成数据属性」,使用时无需加括号调用,同时支持对属性的访问、修改、删除等操作进行精细化控制,是封装思想的进阶实现。
-
基础用法:只读属性(伪装计算属性)
-
适用于 "需要计算得到的属性"(如 BMI、面积、总价),调用时无需加括号,符合属性使用直觉:
pythonclass Human: """示例:计算BMI指数(体重(kg)/身高(m)²)""" def __init__(self, name, weight, height): self.name = name self.weight = weight # 体重(kg) self.height = height # 身高(m) @property def bmi(self): """用property伪装成属性的计算方法""" if self.height <= 0: return "❌ 身高不能为0或负数" bmi_value = self.weight / (self.height ** 2) return f"{self.name}的BMI指数:{bmi_value:.2f}" # 测试基础用法 if __name__ == "__main__": person = Human("熊大", 91, 1.76) print(person.bmi) # 调用时无需加括号,输出:熊大的BMI指数:29.38 # person.bmi = 25 # 未定义setter,修改会报错(只读属性)
-
-
完整控制:读 / 改 / 删(CUDR)
通过
@property(读)、@方法名.setter(改)、@方法名.deleter(删)的组合,实现属性的全生命周期管控,可在操作中添加校验、权限控制等逻辑:pythonclass UserStatus: def __init__(self, username): # 私有属性:外部不可直接访问 self.__username = username # 1. 读(Read):基础property装饰器 @property def username(self): """读取用户名(可添加格式化/权限校验)""" return f"用户名称:{self.__username}" # 2. 改(Update):配套setter装饰器 @username.setter def username(self, new_name): """修改用户名(添加类型/规则校验)""" if not isinstance(new_name, str): raise TypeError("❌ 用户名必须是字符串类型") if len(new_name) < 3 or len(new_name) > 16: raise ValueError("❌ 用户名长度需在3-16位之间") self.__username = new_name print(f"✅ 用户名已修改为:{new_name}") # 3. 删(Delete):配套deleter装饰器 , deleter 方法名必须和 property 一致 @username.deleter def username(self): """控制删除操作(可禁止/添加前置逻辑)""" raise PermissionError("❌ 用户名不允许删除!") # del self.__username # 若允许删除,取消注释即可 # 测试完整属性控制 if __name__ == "__main__": user = UserStatus("xiongda") # 1. 读取属性 print(user.username) # 输出:用户名称:xiongda # 2. 修改属性(触发setter校验) user.username = "bear1" # 合法修改,输出:✅ 用户名已修改为:bear1 # user.username = 123 # 触发TypeError # user.username = "ab" # 触发ValueError # 3. 删除属性(触发deleter限制) try: del user.username # <--删除 except PermissionError as e: print(e) # 输出:❌ 用户名不允许删除! # del user.username:触发 @username.deleter 装饰的删方法。 -
property 核心注意事项
- 命名规范 :
property装饰的方法名需与管控的属性名一致(如username对应__username); - 执行顺序 :必须先定义
@property,再定义setter/deleter(依赖基础方法名); - 只读属性 :仅定义
@property时,属性为只读,强行修改会报AttributeError; - 逻辑增强 :可在
getter/setter/deleter中添加任意逻辑(如日志记录、数据加密、联动修改)。 - 其它补充
- 读、改、删三个方法必须同名;
- 调用方式必须符合 "属性语法"(无括号);
- 删除逻辑通过
del 实例.属性名触发,而非直接调用方法。
- 命名规范 :
1.4、总结
-
核心设计思想
- 封装优先:隐藏内部实现细节,仅暴露最小化的公开接口,保障数据安全与代码稳定性;
- 复用合理:"is-a" 关系用继承,"has-a" 关系用组合,避免多层多继承导致的逻辑混乱;
- 多态解耦:通过统一接口适配不同实现,新增功能时无需修改原有调用逻辑(开闭原则);
- 职责单一:一个类只负责一个核心功能,方法逻辑简洁,命名符合语义。
-
常见问题与解决方案
问题场景 核心原因 解决方案 属性访问报错 私有属性直接访问 / 属性未定义 通过 property或公开接口访问,检查__init__是否初始化多继承方法冲突 MRO 顺序不清晰 / 同名方法未重写 通过 类名.mro()查看查找顺序,子类重写冲突方法方法选型混乱 未区分类 / 实例属性依赖 按 "是否用实例属性→对象方法;是否用类属性→类方法;无依赖→静态方法" 选型 代码冗余 共性逻辑未提取 提取到父类(继承)或独立类(组合),复用核心逻辑
二、对象自省
-
细分归类
操作类型 具体函数 / 关键字 细分归类 核心作用 类型判断 isinstance、issubclass 类型判断 运行时检查对象类型、类的继承关系 类型判断 type 类型判断 获取对象的精确类型(返回类对象) 反射机制 hasattr 属性自省(反射) 检查对象是否拥有指定属性 / 方法 反射机制 getattr 属性自省(反射) 动态获取对象的属性 / 方法(支持默认值) 反射机制 setattr 属性自省(反射) 动态设置对象的属性 / 方法 反射机制 delattr 属性自省(反射) 动态删除对象的属性 / 方法
2.1、类型判断
在Python中,类型判断用于在运行时确认对象的类型、类的继承层级,常用的判断方式有isinstance、issubclass和type三种,三者各司其职,适配不同场景。
| 工具函数 | 核心作用 | 适用场景 |
|---|---|---|
isinstance |
判断实例是否属于指定类(含子类) | 实例类型校验、数据类型判断 |
issubclass |
判断类是否为另一个类的子类(含间接继承) | 继承关系验证、类层级检查 |
type |
获取实例所属的精确类(仅直接类) | 查看实例的原始类型、类型对比 |
-
isinstance :判断对象是否属于指定类型
用于检查一个实例对象是否属于某个类(或其子类),也支持判断基本数据类型,是最常用的类型判断工具。
python# 1. 基本数据类型判断 hello = "world" num = 123 print(isinstance(hello, str)) # True:字符串类型校验 print(isinstance(num, int)) # True:整数类型校验 print(isinstance(num, (int, float))) # True:支持多类型元组校验 # 2. 自定义类实例判断 class Foo: # 自定义类 pass f1 = Foo() print(isinstance(f1, Foo)) # True:f1是Foo的实例 print(isinstance(f1, object)) # True:所有类都继承自object print(isinstance(f1, str)) # False:f1非字符串类型 -
issubclass:判断类之间的继承关系
用于检查一个类是否是另一个类的子类(支持间接继承判断),核心作用是验证类的继承层级。
python# 定义继承链 class Base: # 父类 pass class Foo(Base): # 直接子类 pass class C3(Foo): # 间接子类(孙子类) pass # 1. 直接继承判断 print(issubclass(Foo, Base)) # True:Foo是Base的直接子类 print(issubclass(Base, Foo)) # False:Base非Foo的子类 # 2. 间接继承判断 print(issubclass(C3, Base)) # True:C3间接继承Base # 3. 查看类的完整继承链(MRO) print(C3.__mro__) # 输出:(<class '__main__.C3'>, <class '__main__.Foo'>, <class '__main__.Base'>, <class 'object'>) -
type:获取对象的具体类
用于获取一个实例对象所属的类,返回结果是类本身,与
isinstance的区别是不考虑继承关系(仅判断直接所属类)。pythonclass Foo(Base): pass obj = Foo() # 1. 获取实例所属类 print(type(obj)) # 输出:<class '__main__.Foo'> print(type(obj) == Foo) # True:精准匹配直接类 print(type(obj) == Base) # False:不考虑继承关系 # 2. 对比isinstance与type的差异 print(isinstance(obj, Base)) # True:isinstance考虑继承 print(type(obj) == Base) # False:type不考虑继承 -
类型判断最佳实践
- 日常类型校验优先用
isinstance(兼容继承,更灵活); - 需精准匹配直接类时用
type(如严格区分子类 / 父类实例); - 验证类的继承关系用
issubclass+__mro__(清晰梳理继承链)。
- 日常类型校验优先用
2.2、反射机制
核心定义 :反射是通过「字符串形式的属性名 / 方法名」,在运行时动态访问、修改、删除类 / 对象的属性(包括数据属性和方法),核心工具为hasattr、getattr、setattr、delattr,是实现 "配置驱动""插件化开发" 的核心技术。
-
基础准备:定义测试类和对象
pythonclass Hello: hna = "xion" # 类属性(所有实例共享) def world(self): # 类方法 print(f"Hello {self.name if hasattr(self, 'name') else 'World'}") # 实例化并添加对象独有属性 h1 = Hello() h1.name = "xiong" # 对象属性(仅h1拥有) -
hasattr:判断属性是否存在
用于判断类或对象是否存在指定名称的属性(数据属性或方法),第二个参数必须是字符串形式的属性名。
python# 1. 检查对象属性 print(hasattr(h1, "name")) # True:h1有name属性 print(hasattr(h1, "world")) # True:h1的类有world方法(方法也是属性) print(hasattr(h1, "hna")) # True:继承类的hna属性 print(hasattr(h1, "xxxx")) # False:无此属性 print(hasattr(h1, "World")) # False:属性名区分大小写 # 2. 检查类属性 print(hasattr(Hello, "hna")) # True:类本身有hna属性 print(hasattr(Hello, "world"))# True:类本身有world方法 -
getattr:获取属性值
用于获取类或对象的指定属性值,若属性不存在可指定默认返回值(不指定则报错)。获取方法后可直接调用执行。
python# 1. 获取对象数据属性 print(getattr(h1, "name")) # 输出:xiong print(getattr(h1, "hna")) # 输出:xion(继承类属性) # 2. 获取并执行对象方法 world_func = getattr(h1, "world") world_func() # 输出:Hello xiong # 3. 不存在的属性:指定默认值(推荐) print(getattr(h1, "age", None)) # 输出:None(避免AttributeError) # 4. 不存在且无默认值:触发报错(不推荐) # print(getattr(h1, "age")) # 报错:AttributeError: 'Hello' object has no attribute 'age' # 最佳实践:先判断再获取 if hasattr(h1, "age"): print(getattr(h1, "age")) else: print("属性age不存在") -
setattr:设置属性
用于给类或对象动态添加属性(数据属性或方法),效果等同于
对象.属性=值或类.属性=值。python# 给对象添加数据属性 setattr(h1, "age", 10000) # 等同于 h1.age = 10000 print(h1.__dict__) # 输出:{'name': 'xiong', 'age': 10000} # 给类添加方法 def new_method(self): print(f"Hello {self.name}") setattr(Hello, "new_world", new_method) # 给Hello类添加new_world方法 h1.new_world() # 执行新增方法,输出:Hello xiong -
delattr:删除属性
删除类 / 对象的指定属性,效果等同于
del 对象.属性/del 类.属性,不可删除类的内置属性 (如__init__、__dict__)。python# 1. 删除对象属性 delattr(h1, "age") # print(h1.age) # 报错:AttributeError: 'Hello' object has no attribute 'age' # 2. 删除类属性 delattr(Hello, "version") # print(Hello.version) # 报错:AttributeError: type object 'Hello' has no attribute 'version' # 3. 无法删除内置属性 # delattr(Hello, "__init__") # 报错:AttributeError: __init__ -
示例
pythonclass CommandHandler: def add(self, a, b): return a + b def subtract(self, a, b): return a - b def multiply(self, a, b): return a * b # 反射核心:根据用户输入的字符串调用对应方法 handler = CommandHandler() while True: cmd = input("请输入命令(add/subtract/multiply/exit):") if cmd == "exit": break # 1. 检查方法是否存在 if not hasattr(handler, cmd): print(f"无效命令:{cmd}") continue # 2. 动态获取方法 func = getattr(handler, cmd) # 3. 执行方法 a = int(input("请输入第一个数:")) b = int(input("请输入第二个数:")) result = func(a, b) print(f"结果:{result}") # 输入示例: # 请输入命令(add/subtract/multiply/exit):add # 请输入第一个数:10 # 请输入第二个数:20 # 结果:30 -
反射使用原则
- 安全校验 :反射用户输入的属性名时,必须做白名单校验(避免恶意调用敏感方法,如
__del__、__dict__); - 先判后取 :调用
getattr前,务必用hasattr检查属性是否存在,或直接设置默认值; - 避免滥用 :若仅需处理 2-3 个固定属性 / 方法,用
if/elif更清晰,反射适用于 "属性名动态变化" 的场景; - 作用域明确:区分 "对象属性" 和 "类属性",避免误改类属性导致所有实例受影响。
- 安全校验 :反射用户输入的属性名时,必须做白名单校验(避免恶意调用敏感方法,如
2.3、总结
| 能力分类 | 核心工具 | 核心价值 |
|---|---|---|
| 类型判断 | isinstance/issubclass/type | 运行时识别对象类型、类继承关系,保障数据合法性 |
| 反射机制 | hasattr/getattr/setattr/delattr | 动态操作属性 / 方法,实现配置驱动、插件化开发 |