10、Python面向对象编程-2

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 提供与类 / 实例相关的工具函数(无属性依赖) 类.方法名 () 实例.方法名 ()
  • 示例

    python 复制代码
    class 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
  • 方法选型原则

    • 优先选对象方法 :若方法需要访问 / 修改实例属性(如nameage);

    • 选类方法 :若方法需要访问 / 修改类属性(如school),或需要封装 "实例创建逻辑";

    • 选静态方法:若方法是通用工具(如数据校验、格式转换),且不依赖类 / 实例的任何属性;

    • 禁止滥用 :避免用静态方法实现本应属于对象 / 类方法的逻辑(如静态方法中强行操作self/cls)。

1.3、property 装饰器

property装饰器是Python面向对象编程中用于属性管理的重要工具,核心作用是「将方法伪装成数据属性」,使用时无需加括号调用,同时支持对属性的访问、修改、删除等操作进行精细化控制,是封装思想的进阶实现。

  • 基础用法:只读属性(伪装计算属性)

    • 适用于 "需要计算得到的属性"(如 BMI、面积、总价),调用时无需加括号,符合属性使用直觉:

      python 复制代码
      class 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(删)的组合,实现属性的全生命周期管控,可在操作中添加校验、权限控制等逻辑:

    python 复制代码
    class 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、总结

  • 核心设计思想

    1. 封装优先:隐藏内部实现细节,仅暴露最小化的公开接口,保障数据安全与代码稳定性;
    2. 复用合理:"is-a" 关系用继承,"has-a" 关系用组合,避免多层多继承导致的逻辑混乱;
    3. 多态解耦:通过统一接口适配不同实现,新增功能时无需修改原有调用逻辑(开闭原则);
    4. 职责单一:一个类只负责一个核心功能,方法逻辑简洁,命名符合语义。
  • 常见问题与解决方案

    问题场景 核心原因 解决方案
    属性访问报错 私有属性直接访问 / 属性未定义 通过property或公开接口访问,检查__init__是否初始化
    多继承方法冲突 MRO 顺序不清晰 / 同名方法未重写 通过类名.mro()查看查找顺序,子类重写冲突方法
    方法选型混乱 未区分类 / 实例属性依赖 按 "是否用实例属性→对象方法;是否用类属性→类方法;无依赖→静态方法" 选型
    代码冗余 共性逻辑未提取 提取到父类(继承)或独立类(组合),复用核心逻辑

二、对象自省

  • 细分归类

    操作类型 具体函数 / 关键字 细分归类 核心作用
    类型判断 isinstance、issubclass 类型判断 运行时检查对象类型、类的继承关系
    类型判断 type 类型判断 获取对象的精确类型(返回类对象)
    反射机制 hasattr 属性自省(反射) 检查对象是否拥有指定属性 / 方法
    反射机制 getattr 属性自省(反射) 动态获取对象的属性 / 方法(支持默认值)
    反射机制 setattr 属性自省(反射) 动态设置对象的属性 / 方法
    反射机制 delattr 属性自省(反射) 动态删除对象的属性 / 方法

2.1、类型判断

在Python中,类型判断用于在运行时确认对象的类型、类的继承层级,常用的判断方式有isinstanceissubclasstype三种,三者各司其职,适配不同场景。

工具函数 核心作用 适用场景
isinstance 判断实例是否属于指定类(含子类) 实例类型校验、数据类型判断
issubclass 判断类是否为另一个类的子类(含间接继承) 继承关系验证、类层级检查
type 获取实例所属的精确类(仅直接类) 查看实例的原始类型、类型对比
  1. 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非字符串类型
  2. 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'>)
  3. type:获取对象的具体类

    用于获取一个实例对象所属的类,返回结果是类本身,与isinstance的区别是不考虑继承关系(仅判断直接所属类)。

    python 复制代码
    class 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不考虑继承
  4. 类型判断最佳实践

    • 日常类型校验优先用isinstance(兼容继承,更灵活);
    • 需精准匹配直接类时用type(如严格区分子类 / 父类实例);
    • 验证类的继承关系用issubclass + __mro__(清晰梳理继承链)。

2.2、反射机制

核心定义 :反射是通过「字符串形式的属性名 / 方法名」,在运行时动态访问、修改、删除类 / 对象的属性(包括数据属性和方法),核心工具为hasattrgetattrsetattrdelattr,是实现 "配置驱动""插件化开发" 的核心技术。

  1. 基础准备:定义测试类和对象

    python 复制代码
    class Hello:
        hna = "xion"  # 类属性(所有实例共享)
    
        def world(self):  # 类方法
            print(f"Hello {self.name if hasattr(self, 'name') else 'World'}")
    
    # 实例化并添加对象独有属性
    h1 = Hello()
    h1.name = "xiong"  # 对象属性(仅h1拥有)
  2. 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方法
  3. 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不存在")
  4. 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
  5. 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__
  6. 示例

    python 复制代码
    class 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
  7. 反射使用原则

    • 安全校验 :反射用户输入的属性名时,必须做白名单校验(避免恶意调用敏感方法,如__del____dict__);
    • 先判后取 :调用getattr前,务必用hasattr检查属性是否存在,或直接设置默认值;
    • 避免滥用 :若仅需处理 2-3 个固定属性 / 方法,用if/elif更清晰,反射适用于 "属性名动态变化" 的场景;
    • 作用域明确:区分 "对象属性" 和 "类属性",避免误改类属性导致所有实例受影响。

2.3、总结

能力分类 核心工具 核心价值
类型判断 isinstance/issubclass/type 运行时识别对象类型、类继承关系,保障数据合法性
反射机制 hasattr/getattr/setattr/delattr 动态操作属性 / 方法,实现配置驱动、插件化开发
相关推荐
Jo乔戈里5 小时前
Python复制文件到剪切板
开发语言·python
小鱼儿亮亮5 小时前
SSE传输方式的MCP服务器创建流程
python·mcp
B站_计算机毕业设计之家5 小时前
python招聘数据 求职就业数据可视化平台 大数据毕业设计 BOSS直聘数据可视化分析系统 Flask框架 Echarts可视化 selenium爬虫技术✅
大数据·python·深度学习·考研·信息可视化·数据分析·flask
任子菲阳5 小时前
学Java第五十三天——IO综合练习(1)
java·开发语言·爬虫
子夜江寒5 小时前
Python 学习-Day9-pandas数据导入导出操作
python·学习·pandas
繁华似锦respect5 小时前
单例模式出现多个单例怎么确定初始化顺序?
java·开发语言·c++·单例模式·设计模式·哈希算法·散列表
码农很忙5 小时前
让复杂AI应用构建像搭积木:Spring AI Alibaba Graph深度指南与源码拆解
开发语言·人工智能·python
渡我白衣5 小时前
计算机组成原理(1):计算机发展历程
java·运维·开发语言·网络·c++·笔记·硬件架构
霸王大陆6 小时前
《零基础学 PHP:从入门到实战》模块十:从应用到精通——掌握PHP进阶技术与现代化开发实战-4
开发语言·php