9、Python面向对象编程-1

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、示例
  • 示例

    python 复制代码
    class 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:是实例的 "自身引用",区分不同实例的专属属性 / 方法,谁调用就指向谁;
    • __init__:是实例的 "初始化入口",创建实例时自动执行,通过self给实例绑定初始属性;
    • 核心逻辑:创建实例 → Python 自动传实例给self__init__ 执行 → 通过self完成实例属性初始化。
  • 一句话记住:__init__ 负责给实例 "赋初始值",self 负责定位 "该给哪个实例赋值",两者配合让每个实例都有自己的初始状态。

1.3、属性查找规则与绑定特性

  • 说明
    • "属性查找规则" 是指访问实例 / 类的属性时,解释器从哪里找这个属性
    • "绑定特性" 是指属性(实例属性 / 类属性 / 方法)如何关联到实例或类
1.3.1、查找规则
  1. 先明确:属性的两类核心绑定形式

    属性绑定的核心是 "归属"------ 属性到底属于 还是实例,这决定了它的共享性和查找逻辑:

    • 类属性:绑定到类,所有实例共享

      • 定义方式:直接在类体中(__init__外)赋值;
      • 绑定对象:类本身,所有基于该类创建的实例都能访问;
      • 修改方式 :只能通过类名.类属性修改,实例修改会创建同名实例属性(遮蔽类属性)。
    • 实例属性:绑定到实例,每个实例独有

      • 定义方式 :在__init__(或其他实例方法)中通过self.属性名 = 值赋值;
      • 绑定对象:具体的实例,不同实例的同名属性相互独立;
      • 修改方式 :通过实例名.属性名self.属性名修改,仅影响当前实例。
    • 示例

      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有同名实例属性,仍读自己的)
  2. 属性查找规则:"先实例,后类,再父类"

    当通过实例.属性名访问属性时,Python 解释器会按以下优先级顺序 查找,找到即停止;若全找不到则报AttributeError核心口诀实例找属性,先找自己的,再找类的;类找属性,只找自己的和父类的

    • 原理说明

      • 第一步 :查找当前实例的 "实例属性字典"(__dict__),看是否有该属性;
      • 第二步:若实例没有,查找实例所属类的 "类属性字典",看是否有该属性;
      • 第三步:若类没有,按继承关系向上查找父类的类属性(多继承有 MRO 规则,此处简化);
      • 第四步 :若都找不到,触发AttributeError
    • 示例

      python 复制代码
      class 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'
    • 查找规则的关键细节

      1. 实例属性会 "遮蔽" 类属性

        • 如果实例有和类同名的属性,查找时会优先返回实例属性(类属性被 "隐藏"),如前面Student示例中s1.school
      2. 类无法访问实例属性

        • 通过类名.属性名查找时,只会找类属性 / 父类属性,不会找实例属性(因为实例属性属于具体实例,不属于类)。
      3. 动态绑定属性

        • Python 支持 "运行时动态绑定属性"------ 即使不在__init__中定义,也能给实例 / 类新增属性:

          python 复制代码
          class Cat:
              pass
          
          # 动态给类绑定属性
          Cat.color = "黑色"
          # 动态给实例绑定属性
          c = Cat()
          c.name = "小黑"
          
          print(Cat.color)  # 输出:黑色
          print(c.name)     # 输出:小黑
          print(c.color)    # 输出:黑色(实例找color,先找自己没有,再找类的)
1.3.2、方法的绑定特性

方法本质是 "可调用的属性",绑定规则和普通属性略有不同:

  1. 实例方法(带 self):绑定到实例

    • 定义:def 方法名(self, ...):
    • 调用:实例.方法名() → Python 自动把实例传给 self;
    • 特性:必须通过实例调用(或手动传类实例给 self),依赖实例的属性。
  2. 类方法(带 cls):绑定到类

    • 定义:@classmethod装饰 + def 方法名(cls, ...):
    • 调用:类名.方法名()实例.方法名() → Python 自动把类传给 cls;
    • 特性:操作类属性,不依赖实例。
  3. 静态方法:无绑定(独立函数)

    • 定义:@staticmethod装饰 + def 方法名(...):(无 self/cls);
    • 调用:类名.方法名()实例.方法名()
    • 特性:和类 / 实例无关,仅逻辑上归属类。
  4. 示例

    python 复制代码
    class 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 通过 "双下划线前缀(__)" 实现成员私有化(本质是名称修饰,非严格私有),私有成员仅能在类内部访问,外部需通过公开接口操作。

    python 复制代码
    class 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)封装,在方法中添加数据校验、格式转换等逻辑,保证数据合法性。

      python 复制代码
      class 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, 参数) 多继承精准调用 不依赖继承顺序,精准指定父类 硬编码父类名,耦合度高
    • 通过「父类名」直接调用(通用,支持多继承)

      python 复制代码
      class Parent:
          def __init__(self):
              print("父类初始化方法")
      
      class Child(Parent):
          def __init__(self):
              # 第一步:调用父类的__init__(手动传self)
              Parent.__init__(self)  
              # 第二步:执行子类自己的初始化逻辑
              print("子类初始化方法")
      
      c1 = Child()
      # 输出顺序:
      # 父类初始化方法
      # 子类初始化方法
    • 通过 super() 调用(推荐,适配 MRO 顺序)

      python 复制代码
      class Parent:
          def __init__(self):
              print("父类初始化方法")
      
      class Child(Parent):
          def __init__(self):
              # 第一步:调用父类的__init__(无需传self,super自动处理)
              super().__init__()  
              # 第二步:执行子类自己的初始化逻辑
              print("子类初始化方法")
      
      c1 = Child()
      # 输出和方式1一致:
      # 父类初始化方法
      # 子类初始化方法
    • 如果两种都不写 <-- 异常说明

      python 复制代码
      class Parent:
          def __init__(self):
              print("父类初始化方法")
      
      class Child(Parent):
          def __init__(self):
              print("子类初始化方法")
      
      c1 = Child()
      c1.Parent()
      • c1Child 的实例,它的属性只有 Child 类中通过 self.xxx 绑定的内容,没有 Parent 这个属性

      • 若强行想通过实例访问父类,正确的方式是先通过 type(c1).__bases__ 拿到父类,再调用,但实际开发中绝对不推荐

        python 复制代码
        c1 = Child()
        # 错误写法
        # c1.Parent.__init__()  # 直接报错:'Child' object has no attribute 'Parent'
        
        # 强行通过实例找父类并调用(仅作原理演示,禁止实际使用)
        ParentClass = type(c1).__bases__[0]  # 拿到Child的第一个父类(Parent)
        ParentClass.__init__(c1)  # 手动传实例c1,调用父类__init__
        # 输出:父类初始化方法
    • 两种加上函数

      python 复制代码
      class 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 线性化算法。

  1. 多继承基础示例

    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()  # 输出:拥有游泳能力!
  2. 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方法
  3. 菱形继承与多继承建议

    • 菱形继承:子类继承多个父类,且多个父类最终继承同一个基类(如所有类最终继承 object),此时 MRO 顺序尤为重要。
    • 多继承使用建议:
      • 尽量避免「多层多继承」,复杂度高且易出现逻辑冲突;
      • 若需整合多个独立功能(如飞行+游泳),优先使用「组合」而非多继承;
      • 多继承场景下,务必通过 mro() 确认方法查找顺序,避免同名方法冲突。

2.3 多态

多态是指 "不同子类继承同一父类后,重写父类同名方法,使得同一接口调用能适配不同子类的实现"。核心是 "接口统一,实现各异",大幅提升代码的可扩展性。

  • 多态的核心要素

    1. 存在继承关系(子类继承同一父类);
    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模块定义抽象基类,可强制子类实现指定方法,保证接口统一(适合团队开发 / 框架设计)。

      python 复制代码
      import 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("管理员") 时,userAdmin 实例,调用 Admin.operate()
          • 当传入 NormalUser("用户") 时,userNormalUser 实例,调用 NormalUser.operate()
        • 这就是多态的核心:同一个接口(operate()),不同实例表现出不同行为
相关推荐
say_fall1 小时前
C++ 入门第一课:命名空间、IO 流、缺省参数与函数重载全解析
c语言·开发语言·c++
霸王大陆1 小时前
《零基础学 PHP:从入门到实战》模块十一:成为 PHP 侦探,精通错误处理与调试实战大全-1
开发语言·笔记·php·课程设计
郝学胜-神的一滴1 小时前
Python的内置类型:深入理解与使用指南
开发语言·python·程序人生
松☆1 小时前
C语言--结构体
c语言·开发语言
刘家炫1 小时前
Linux 基于 Epoll 的主从 Reactor 多线程模型
linux·服务器·reactor·项目·多路转接
关于不上作者榜就原神启动那件事1 小时前
【java后端开发问题合集】
java·开发语言
LitchiCheng2 小时前
Mujoco 蒙特卡洛采样统计机械臂可达工作空间(非Matlab)
开发语言·matlab
真正的醒悟2 小时前
图解网络8
开发语言·网络·php
徐_三岁2 小时前
Python 入门学习
java·python·学习