python复习--对象相关--对象生命周期
一. 一句话总览版
Python 对象的生命周期是:
创建 → 被引用 → 引用变化 → 不可达 → 回收
Python 只关心“引用”,不关心“是否使用”。
二、Python 世界里最重要的 3 个概念
1.对象(Object)
- 真正存在于内存中的东西
- 例如:整数、字符串、函数、类、列表……
2.名字(Name / 变量名)
- 只是一个引用标签
- 本身不存数据
3.引用(Reference)
- 名字 / 容器 / 属性 → 对象 的指向关系
名字 ──▶ 对象
名字 ≠ 对象
三、对象生命周期第 1 阶段:创建(Creation)
- 对象只会在执行“创建语句”时创建。
1. 常见的创建方式
绑定名字
x ──▶ <listobject>名字 x 指向这个 list 对象
引用计数 = 1Python 不会“先创建一个没有引用的对象,再找名字给它”
这两步在语义上是原子完成的。
创建对象
[]在内存中创建一个 list 对象
对象此时 必须被某个引用接住
逐字拆解 x = [] 到“解释器视角”
x =[]在这一行里,发生了 两个动作(但对你来说像一步):
示例
a =10# 创建 int 对象 b =[]# 创建 list 对象 c ={}# 创建 dict 对象 d =lambda x: x # 创建函数对象[] 是对象本身(一个 list 对象)
b 不是对象,只是一个名字(引用)
b = [] 的含义是:
创建一个 list 对象,然后让名字 b 指向它
2. def / class 也是“创建对象”
执行到这行时:
创建函数 / 类对象
绑定名字
| 语法 | 创建的对象 | 绑定的名字 |
|---|---|---|
| def f() | 函数对象 | f |
| class A | 类对象 | A |
示例
deff():passclassA:pass四、对象生命周期第 2 阶段:被引用(Alive)
1.示例
- 内存关系:f ──▶ <function f>
- 此时:
引用数 ≥ 1
对象是“活着的”
GC 不会碰它
示例
deff():pass2.引用是怎么变化的?(这是核心)
引用 -1 的情况
b =Nonea ───▶ []
引用数 = 1(对象仍活着)
引用 +1 的情况
a =[] b = a a ─┐
├──▶ []
b ─┘
引用数 = 2
五、对象生命周期第 3 阶段:不可达(Unreachable)
1. 关键规则
当一个对象没有任何引用指向它时,它就“不可达”
示例
f =[] f =None执行过程:
f = []:创建 list,对象被 f 引用 (引用=1)
f = None:解除引用 (引用=0)执行结束后:
没有任何名字指向该 list
引用计数 = 0
对象不可达
等待 / 立即回收(CPython)
2. 补充(需要注意)
不可达 ≠ 立刻销毁(在所有实现中)
CPython:引用计数 → 通常立刻回收
其他实现(PyPy):可能延迟回收
六、对象生命周期第 4 阶段:回收(Garbage Collection)
- Python 的 GC 两层机制
1. 第一层:引用计数(最核心、最常用)
- CPython 中,每个对象都有 refcount,这是 CPython 的基础机制。
什么是引用计数?
每个对象内部都有一个 refcount
每多一个引用,计数 +1
每少一个引用,计数 -1
当 refcount == 0 → 对象立刻销毁(大多数情况)
示例:
a =[] a =Nonea = [] → 引用数 = 1
a = None → 引用数 = 0 → 回收
2. 第二层:循环垃圾回收(Cycle GC)
- 为了解决循环引用,Python 引入了第二层机制。
即使这样:
a =None b =None你会以为对象可以被回收,但实际上:a 和 b 互相引用
各自的 refcount 都不为 0
引用计数机制 无法回收它们
循环引用问题(引用计数解决不了)
a =[] b =[] a.append(b) b.append(a)此时结构是:
a → b ↑ ↓ └───┘ 循环 GC 的核心思想
只要对象组“从程序中不可达”,就应该被回收
不管它们内部怎么互相引用。
a =[] b =[] a.append(b) b.append(a) a =None b =None- 此时:
程序中已经没有任何变量能访问这两个列表
虽然它们内部还互相引用
循环 GC 会发现它们不可达,并最终回收
3. 表格记忆
| 机制 | 解决什么 | 特点 |
|---|---|---|
| 引用计数 | 普通对象回收 | 快、实时 |
| 循环 GC | 循环引用 | 定期扫描、补救机制 |
99% 情况靠引用计数
1% 循环引用靠 GC 扫描
七、一个“完整生命周期时间线”示例(重点)
1. 示例
defmake(): x =[]return x a = make() b = a a =None b =None执行过程:
| 时刻 | 状态 |
|---|---|
| x = [] | 创建 list,对象一出生就被 x 引用,引用数 = 1 |
| return x | 返回引用,返回的是对象的引用,不是新对象 |
| a = make() | a 指向 list,引用数 = 1 |
| b = a | 引用 +1 |
| a = None | 引用 -1 |
| b = None | 引用 -1,引用 = 0 → 回收 |
这里的 a 和 b 这两个名字,绑定到了同一个 list 对象上
八、装饰器中的对象生命周期
1. 示例一:装饰器保存原函数引用
defdecorator(func):defwrapper(): func()return wrapper @decoratordeff():print("hi")装饰器在函数定义阶段执行,等价于:
deff():print("hi")defdecorator(func):defwrapper(): func()return wrapper f = decorator(f)逐步执行过程(对象 + 引用)
- 原函数为什么没被回收?
因为:
wrapper.closure → func → f_original
虽然名字 f 不再指向原函数
但 wrapper 闭包仍然引用它
f_original 仍然存活
总结一句话, 装饰器通过闭包,延长了原函数对象的生命周期
返回 wrapper 并重新绑定名字
f = wrapper 原来的 f 名字不再指向 f_original
f → wrapper
创建 wrapper 函数对象
defwrapper(): func()创建 wrapper 函数对象
wrapper 闭包捕获了 func
func → f_original
调用装饰器
decorator(f_original)func 参数 → 引用 f_original
创建原函数对象
deff():print("hi")创建 函数对象 f_original
名字 f → 绑定到 f_original
2. 示例二:装饰器不保存原函数引用
defdecorator(func):returnlambda:print("hi")@decoratordeff():print("hi")等价于:
deff():print("hi")defdecorator(func):returnlambda:print("hi") f = decorator(f)执行过程
- 创建原函数对象创建 f_original
f → f_original - 结果
f_original 变为不可达对象
引用计数 = 0
立即被回收
名字重新绑定
f = lambda_func 原来的 f 不再指向 f_original
没有任何引用指向 f_original
调用 decorator
decorator(f_original)但 返回的 lambda 没有引用 func
3. 两种装饰器的“生命周期对比表”
| 情况 | 原函数是否被保存 | 原函数生命周期 |
|---|---|---|
| wrapper 使用 func | 是(闭包) | 存活 |
| 不使用 func | 否 | 立即回收 |
九、对象“不会被回收”的常见原因
1. 还有全局引用
cache =[] cache.append(obj)2. 被闭包捕获
defouter(): x =[]definner():return x return inner 3. 存在循环引用 + del
classA:def__del__(self):pass可能导致无法回收
十、你现在可以用这段话“完美描述对象生命周期”
Python 对象在执行创建语句时生成,
通过名字、容器或属性被引用;
在运行过程中引用动态变化;
当对象不再被任何引用指向时变为不可达;
随后由引用计数或 GC 回收,生命周期结束。
这是解释器级理解
十一、最终总结(必记)
Python 不关心是否调用
Python 不预测未来代码
Python 只在当前时刻检查引用
对象一旦不可达,永远无法复活