OOP-类与对象

本章学习知识点

  • 类与对象:class 定义类、init 构造方法、self 实例引用、属性绑定特性、属性查找规则

面向对象编程(Object-Oriented Programming,简称 OOP)是一种以「对象」为核心的编程思想,它将数据(属性)和操作数据的方法封装在一起,通过封装、继承、多态三大核心特性实现代码复用和逻辑解耦,是 Python 开发中不可或缺的核心技术。

一、类与对象

类(Class)是对一类事物相似特征与行为的抽象总结(如“教师类”包含姓名、授课技能等共性),而对象是类的具体实例(如“张老师”“李老师”)。类比现实世界:类是“汽车设计图纸”,对象是“根据图纸造出的具体汽车”——类是模板,对象是模板创建的实体。

1.1、类的定义与核心规则

类是对象的抽象模板,其定义和使用遵循明确的核心规则,是理解 OOP 的基础:

  • 类的定义方式:通过 class 关键字定义,语法为 class 类名:,类体包含属性(数据)和方法(函数);
  • 核心执行特性
    • 定义顺序:必须先定义类,后创建对象(与函数 “先定义后调用” 逻辑一致);
    • 执行时机:类体代码在「类定义阶段」立即执行,生成类的名称空间(存放类的所有属性和方法);
    • 调用差异:调用函数返回执行结果,调用类(实例化)返回对象本身;
  • 关键操作
    • 类.__dict__:查看类的名称空间(包含所有类属性和方法的键值对);
    • 点语法(.):Python 专属的属性访问方式,如 类.属性对象.属性

1.2 self 与 init(构造 / 初始化方法)

self__init__ 是类实例化和属性初始化的核心,两者配合完成实例的 “初始状态构建”。

1.2.1、核心概念
  • self:实例的自身引用

    self 是 Python 类中实例方法的必选第一个参数(约定俗成的命名,非关键字),核心作用是:

    • 指代当前调用方法的实例本身(谁调用方法,self 就指向谁);
    • 通过 self 访问 / 修改实例的专属属性、调用实例的其他方法;
    • 调用实例方法时,Python 会自动把实例传给 self,无需手动传参。
  • init:实例的初始化方法

    __init__ 是 Python 类的特殊内置方法(也叫构造方法),核心作用是:

    • 创建实例时自动触发执行,无需手动调用;
    • 用于给实例绑定初始属性,完成实例的初始化;
    • 必须以 self 为第一个参数,支持自定义参数,且不能返回非 None 的值。
  • 两者关系

    • __init__ 是 “给实例装初始属性” 的流程,self 是这个流程中 “被装修的那个实例”
    • 通过self__init__中绑定的属性,会成为每个实例的专属数据。
1.2.2、示例
  • 示例

    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()  # 输出:【学校】:北京大学...【姓名】:张三...
    
    print("-" * 50)
    
    # 2. 创建第二个学生实例:self此时指向s2,绑定s2的专属属性
    s2 = Student("李四", 19, 88)
    s2.show_info()  # 输出:【学校】:北京大学...【姓名】:李四...
    
    # 3. 错误示例:分数不合法(__init__中调用check_score触发报错)
    # s3 = Student("王五", 20, 105)  # 报错:王五的分数105不合法!必须在0-100之间
    
    # 查看实例的名称空间(独有属性)
    print(s1.__dict__)  # 输出:{'name': '张三', 'age': 18, 'score': 95}
    
  • 常见误区

    • 忘记 self 参数:实例方法缺少 self 会导致调用时参数不匹配报错;
    • 属性绑错位置:__init__ 中直接写 name = name(少 self),会变成局部变量,实例无法访问;
    • 手动调用 __init__:无需手动执行 s1.__init__(),重复调用可能覆盖已有属性;
    • self 不是关键字:可以用 this 代替,但违反 Python 规范,降低可读性;
    • __init__ 有返回值:__init__ 只能返回 None,返回其他值会报错。
  • 总结

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

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

  • 说明
    • “属性查找规则” 是指访问实例 / 类的属性时,解释器从哪里找这个属性
    • “绑定特性” 是指属性(含方法)如何关联到实例或类(归谁所有)。
1.3.1、查找规则

当通过 实例.属性类.属性 访问属性时,Python 解释器会按固定顺序搜索,核心口诀:实例找属性,先找自己的,再找类的,最后找父类的;类找属性,只找自己的和父类的

  1. 实例访问属性的搜索顺序

    • 顺序说明实例.__dict__(自身实例属性) → 类.__dict__(类属性) → 父类.dict(父类属性)→ … → 顶级父类,找到即停止;若全找不到则报 AttributeError

    • 示例

      # 定义父类
      class Base:
          default = 0  # 父类属性
      
      # 定义子类(继承Base)
      class Person(Base):
          species = "人类"  # 子类类属性
          def __init__(self, name):
              self.name = name  # 实例属性
      
      # 创建实例
      p = Person("张三")
      
      # 第一步:找实例属性(p.__dict__)→ 找到name,返回“张三”
      print(p.name)
      
      # 第二步:实例无species,找类属性(Person.__dict__)→ 返回“人类”
      print(p.species)
      
      # 第三步:实例/类无default,找父类属性(Base.__dict__)→ 返回0
      print(p.default)
      
      # 第四步:实例/类/父类均无age → 报错
      # print(p.age)  # AttributeError: 'Person' object has no attribute 'age'
      
  2. 类访问属性的搜索顺序

    • 顺序说明类.__dict__(自身类属性) → 父类.dict(父类属性)→ … → 顶级父类,类永远无法访问实例属性(实例属性属于具体实例,不属于类)。
  3. 查找规则的关键细节
    • 实例属性遮蔽类属性:若实例有和类同名的属性,实例访问时会优先返回自身属性(类属性被 “隐藏”);
    • 动态绑定属性也遵循规则:Python 支持运行时给类 / 实例新增属性,新增的属性同样按上述顺序查找。
  4. 示例代码(动态绑定 + 遮蔽特性)

    class Cat:
        pass
    
    Cat.color = "黑色"   # 动态给类绑定属性(类.__dict__新增color)
    c = Cat()
    c.name = "小黑"	   # 动态给实例绑定属性(实例.__dict__新增name)
    c.color = "白色"	   # 动态给实例绑定与类同名的属性(遮蔽类属性)
    
    print(Cat.color)  	# 类找属性:Cat.__dict__ → 黑色
    print(c.name)     	# 实例找属性:c.__dict__ → 小黑
    print(c.color)    	# 实例找属性:c.__dict__有 → 白色(遮蔽类属性)
    
1.3.2、方法的绑定特性

属性的绑定本质是 “归属关系”—— 确定属性是属于类(所有实例共享),还是属于某个具体实例(仅该实例独有),核心分为普通属性绑定方法绑定两类:

  1. 普通属性的绑定(类属性 vs 实例属性)

    类型定义方式绑定对象共享性修改方式核心特点
    类属性类体中(__init__ 外)直接赋值类本身所有实例共享仅通过 类名.属性 修改改则全改,实例仅能读取(除非遮蔽)
    实例属性__init__/ 实例方法中 self.属性具体实例每个实例独有实例名.属性self.属性 修改
    • 示例

      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)  # 报错:AttributeError(类无name属性)
      
      # 3. 实例“修改”类属性:实际是创建同名实例属性(遮蔽类属性)
      s1.school = "清华大学"  # 给s1新增实例属性school,非修改类属性
      print(s1.school)       # 输出:清华大学(优先读实例属性)
      print(s2.school)       # 输出:北京大学(类属性未变)
      print(Student.school)  # 输出:北京大学(类属性未变)
      
      # 4. 真正修改类属性:必须通过类名修改(所有实例共享变化)
      Student.school = "复旦大学"
      print(s2.school)  # 输出:复旦大学(s2无同名实例属性,读类属性)
      print(s1.school)  # 输出:清华大学(s1有同名实例属性,仍读自己的)
      
  2. 方法的绑定特性(可调用的特殊属性)

    方法本质是 “可调用的属性”,绑定规则与普通属性不同,核心分为三类:

    方法类型定义方式绑定对象调用方式核心参数适用场景
    实例方法def 方法名(self, ...):实例实例.方法名()self操作实例属性(如读取 / 修改 name)
    类方法@classmethod + def 方法名(cls, ...):类名.方法名()/实例.方法名()cls操作类属性(如修改 school)
    静态方法@staticmethod + def 方法名(...):无绑定类名.方法名()/实例.方法名()通用工具函数(与类 / 实例无关)
    • 示例

      class Dog:
          type = "犬科"  # 类属性
      
          # 实例方法:绑定到实例,依赖self(操作实例属性)
          def __init__(self, name):
              self.name = name  # 实例属性
      
          def bark(self):
              print(f"{self.name}:汪汪叫")  # 依赖实例属性name
      
          # 类方法:绑定到类,依赖cls(操作类属性)
          @classmethod
          def change_type(cls, new_type):
              cls.type = new_type  # 修改类属性type
      
          # 静态方法:无绑定,仅逻辑归属类
          @staticmethod
          def run():
              print("狗狗在跑")  # 不依赖任何类/实例属性
      
      # 1. 实例方法:绑定实例,self自动指向调用实例(d1)
      d1 = Dog("旺财")
      d1.bark()  # 输出:旺财:汪汪叫
      
      # 2. 类方法:绑定类,cls自动指向Dog类(实例调用也指向类)
      Dog.change_type("哺乳动物")
      print(Dog.type)  # 输出:哺乳动物
      d1.change_type("食肉动物")  # 实例调用类方法,cls仍指向Dog
      print(Dog.type)  # 输出:食肉动物
      
      # 3. 静态方法:无绑定,类/实例调用效果完全一致
      Dog.run()  # 输出:狗狗在跑
      d1.run()   # 输出:狗狗在跑
      
  3. 核心总结

    • 查找规则:实例找属性 “先自身→类→父类”,类找属性 “仅类→父类”;实例属性会遮蔽同名类属性,动态绑定的属性也遵循此规则;
    • 绑定特性:类属性共享、实例属性独有;实例方法绑定实例(依赖 self)、类方法绑定类(依赖 cls)、静态方法无绑定;
    • 关键注意:修改类属性必须通过类名.属性,实例 “修改” 类属性实际是新增实例属性;类永远无法访问实例属性。
Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐