-类 Lecture 11 - Classes

官方信息技术老师·18 页·深入(追求细节与边界)·0 次浏览·2 天前
C++ 类面向对象自定义类型对象生命周期

Python 类与面向对象

看完你能亲手写出一个完整的类并讲清每个成员的来龙去脉

按 空格/→ 演示下一步

1 / 18 页

全部页面点击任意一页,跳回舞台从这页播放

C++ 类面向对象自定义类型对象生命周期

Python 类与面向对象

看完你能亲手写出一个完整的类并讲清每个成员的来龙去脉

1第 1 页 · Python 类与面向对象

为什么需要类

我们用字典和列表组织过学生信息:姓名、成绩散落在不同结构里,靠位置和键名记忆。这正是内置类型暴露的边界。

数据与行为分离
内置类型只装数据,方法散落在外部函数中
结构难以约束
字典靠字符串键约定,漏字段不会报错
类把数据行为封装
字段和方法绑定成一个新类型,对象即实例
建筑图纸对应 →类与对象

图纸定义结构,对象是按图盖出的具体房子

2第 2 页 · 为什么需要类

内置类型 vs 用户定义类型

int/str 看似基本值,实是对象;自定义类看似新东西,本质也是对象。真实分界在哪?

内置类型 int/str
  • Python 自带,C 实现于解释器
  • 字面量直接写出就是对象
  • 行为固定,不能改内部方法
用户定义类型 class
  • 开发者用 class 关键字自己写
  • 必须调用类名加括号实例化
  • 可继承/重写,行为完全可定制
二者运行时都是对象,没有本质鸿沟;区别只在'谁写类'与'能否扩展'。
3第 3 页 · 内置类型 vs 用户定义类型

类的基本语法

python

用最少代码演示三件事:class 关键字、大驼峰命名、pass 占位。

代码高亮加载中…

L2 是声明入口;L3 解决空类体的语法陷阱;L7 证明该类已成为运行时类型。

4第 4 页 · 类的基本语法

创建类的实例

前面我们写好了类的『图纸』,但图纸本身盖不出能住的房子。要住人,还得拿图纸实实在在地盖一栋——这个动作,就是实例化。

类名加括号即实例化
写 Dog(),Python 就分配内存、生成一个新实例对象
__init__ 自动调用
实例化时 Python 立刻执行它,self 形参就是这个新实例
实例各自独立存数据
各自占独立 __dict__,改 a.name 不影响 b.name
方法自动绑定 self
调 a.bark() 时 Python 自动把 a 作为第一参数传入
按同一份图纸盖多栋房对应 →由类生成多个实例

图纸是类,每栋盖好的房是独立实例,互相不影响

5第 5 页 · 创建类的实例

实例化环境图

顺箭头读:上方是类定义后的状态,下方是实例化流水线。虚线箭头表示属性查找时实例向类的回溯方向。

图解渲染中…
C类 __dict__:存方法与类属性,所有实例共享读取G实例 __dict__:存本实例独有属性,与类字典隔离E__new__ 负责建对象,__init__ 负责初始化,二者解耦
6第 6 页 · 实例化环境图

实例属性

上一页我们画出实例的环境图,看到每个实例都有自己独立的命名空间。这一页聚焦那个空间里真正存放的"数据"——实例属性,并比较它和类中定义的变量到底有什么区别。

定义在 __init__ 中
通过 self.属性名 = 值 给每个实例单独绑定数据
存在实例的 __dict__
实例.__dict__ 是它独有的字典,类.__dict__ 看不到
读先实例后类
读:实例和类都会找;写:实例.属性 = 只改实例自身
修改互不影响
a.name = 'A' 不会改变 b.name,默认各管各的
学校校规 vs 个人成绩对应 →类属性 vs 实例属性

校规全校共享一份,个人成绩每人各自一份

7第 7 页 · 实例属性

属性查找链

从 obj.attr 出发,沿实例→类→父类→object 逐级查找的决策路径

图解渲染中…
a2实例自身的 __dict__,存实例属性b1Dog 类定义的方法与类属性b2Animal 父类继承来的属性b3所有类最终继承的 object
8第 8 页 · 属性查找链

什么是方法

上页我们沿属性查找链追到类。但若在类里找到的是一个函数,Python 不会原样返回——它会先把实例绑进去,再交还给你。这个绑定动作,正是方法的灵魂。

类体内定义的函数
本质上与 def 一样,是函数对象,住在类的 __dict__ 里
self 自动传入
调用时 Python 把实例作为第一个参数注入,self 只是约定名
需要访问实例状态
方法存在的理由:读写实例属性,孤立函数做不到这一点
实例/类/静态方法
实例方法收 self;@classmethod 收 cls;@staticmethod 不收额外参数
私人医生对应 →方法

医生(方法)绑定一位患者(实例 self),直接调取其病历(属性)

obj.f(x)Class.f(obj,x)——绑定动作的等价表达`obj.f(x)` ⇔ `Class.f(obj, x)` —— 绑定动作的等价表达
9第 9 页 · 什么是方法

方法的定义语法

python

用计数器实例展示类内 def 语法:self 绑定当前实例,amount 可省略并采用默认值。

代码高亮加载中…

add 的 self 在实例调用时自动绑定;amount 默认 1。counter.add(3) 覆盖默认值,counter.add() 沿用默认值。

10第 10 页 · 方法的定义语法

self 参数详解

前面知道,方法定义在类里,调用却要通过某个实例。方法要读写属性时,必须先拿到“这个实例”,self 就是专门接住它的第一个参数,再从这里访问实例数据。

实例自动传入
普通实例方法中,调用 obj.method(...) 时,obj 自动成为第一个参数;self 在方法里接住它
读写实例状态
self.attr 读取属性,self.attr = value 只修改当前实例,不改类或其他实例
只是参数名
self 不是 Python 关键字,换名也能运行;规范写法仍统一使用 self
类名直接调用
Class.method(...) 不会自动提供实例,需显式补上 obj,否则会因缺少 self 报参数错误
给具体桌号下单对应 →self 接收实例

桌号对应对象,self 对应该实例;方法凭它找到并修改这张桌子的状态

obj.method(a)type(obj).method(obj, a)\text{obj.method(a)} \equiv \text{type(obj).method(obj, a)}
11第 11 页 · self 参数详解

初始化方法 __init__

python

演示 __init__ 在实例化时如何自动执行,以及如何把参数写入实例属性。

代码高亮加载中…

L4 定义 __init__;L6 把参数写入 self;L13 实例化时自动调用 __init__;L14 验证属性已生效。

12第 12 页 · 初始化方法 __init__

方法调用环境变化

看一次方法调用中栈帧的诞生与销毁。从左到右扫参与方,从上到下扫消息。

图解渲染中…
M方法被调用时才存在的临时栈帧,return 后立刻销毁C实例属性查不到时,会沿类上溯,体现属性查找链
13第 13 页 · 方法调用环境变化

整数集合需求分析

上一节我们学会了写方法,但「能写」和「会设计」差得很远。设计 SetOfIntegers 的第一步,不是敲代码,而是想清楚——它究竟要做什么、边界在哪。

数据存储
内部用一个列表保存所有元素
添加元素
新值追加;重复则跳过,遵循集合去重语义
删除元素
按值移除;不存在要给出处理,边界关键
成员判断
查询某整数是否在集合中,返回布尔
辅助操作
取大小、清空、遍历等只读便利方法
口袋里的硬币对应 →整数集合

投进去=add,掏出来=remove,摸一下=成员判断

14第 14 页 · 整数集合需求分析

类的完整实现

python

把 IntSet 类一次性看全:六个方法各司其职,组成真正可用的集合。

代码高亮加载中…

高亮行就是类的骨架:__init__ 建属性、insert 去重、__contains__ 让 in 工作、remove 抛自定义错误、__repr__ 控制显示——六个方法拼出最小可用类。

15第 15 页 · 类的完整实现

集合类环境图

从全局帧出发,沿「类定义→实例化调用→__init__执行」三段路径,看实例对象与属性的最终归属。

图解渲染中…
g1最外层作用域,类与模块级名字都绑定在此lf1__init__执行时的栈帧,self与vals都是局部名self1self不是数据副本,而是指向新实例的引用av1实例属性通过self.vals=…写入对象自身
16第 16 页 · 集合类环境图

自测检验

点击作答

Python 中执行 obj.attr 时,若实例上没有该属性,Python 会按什么顺序继续查找?

17第 17 页 · 自测检验

课后思考

先自己思考再看答案。三题分别对应回顾、应用与边界。

1实例方法的第一个参数为什么通常叫 self?换个名字(比如 this)还能正常工作吗?为什么?

参考答案这只是约定不是语法要求。Python 不管第一参数叫什么都会传入实例,改名 this 照样运行。但违反 PEP 8 约定会让代码难以阅读。

2如果类的函数体外和 __init__ 中都定义了同名属性,实例化时会得到哪个?试着写代码验证。

参考答案函数体外属性在类定义时只求值一次;__init__ 在每次实例化时执行,对实例赋同名属性后会「遮蔽」类属性。从实例访问时先查实例,查不到再查类。

3在创建实例后修改类级别的属性,已有实例会看到这个改动吗?什么情况下反而看不到?

参考答案会看到,因为实例查找属性时找不到就上溯到类。但若已有实例在 __init__ 中给同名属性赋值过,就形成「实例遮蔽」,后续修改类属性对它们不再生效。

18第 18 页 · 课后思考