在 Python 中,del 语句经常被误认为是“直接删除对象”。实际上它与 Python 的 垃圾回收(Garbage Collection, GC)机制之间存在较深层的关系。理解这一点需要从 对象引用、引用计数、循环引用以及 GC 机制几个层面来看。
一、del语句的本质:删除引用,而不是对象
在 Python 中,一切皆对象,而变量只是 对象的引用(reference)。
del 的作用是:
👉 删除变量名与对象之间的绑定关系(引用)
示例:
a = [1, 2, 3]
b = a
del a
此时:
a被删除- 列表对象
[1,2,3]仍然存在 - 因为
b仍然引用它
内存结构可以理解为:
a ----\
--> [1,2,3]
b ----/
执行:
del a
变成:
b ----> [1,2,3]
因此:
del删除的是 变量引用,而不是对象本身。
二、Python垃圾回收的核心机制:引用计数
Python最基础的垃圾回收机制是 引用计数(Reference Counting)。
每个对象维护一个 引用计数器:
对象引用数 +1
对象引用数 -1
当:
引用计数 == 0
对象会 立即被释放内存。
示例:
a = [1,2,3] # ref = 1
b = a # ref = 2
del a # ref = 1
del b # ref = 0 -> 对象释放
内部逻辑:
Py_DECREF(obj)
if refcount == 0:
free(obj)
优点:
- 回收速度快
- 实时回收
缺点:
- 无法处理循环引用
三、循环引用问题
循环引用是引用计数机制最大的缺陷。
示例:
class Node:
pass
a = Node()
b = Node()
a.ref = b
b.ref = a
del a
del b
对象结构:
a -> NodeA -> NodeB
^ |
|________|
即使:
del a
del b
仍然存在:
NodeA.ref -> NodeB
NodeB.ref -> NodeA
引用计数:
NodeA = 1
NodeB = 1
所以:
👉 引用计数永远不会变成 0
这就造成了 内存泄漏。
四、Python的解决方案:分代垃圾回收(Generational GC)
为了处理循环引用,Python引入了 GC模块。
相关模块:
import gc
Python GC 使用 分代回收策略(Generational Garbage Collection)。
对象被分为三代:
| 代 | 特点 |
|---|---|
| Generation 0 | 新创建对象 |
| Generation 1 | 存活较久对象 |
| Generation 2 | 长期对象 |
回收逻辑:
新对象 -> Gen0
多次存活 -> Gen1
长期存活 -> Gen2
GC 只会 扫描可能存在循环引用的容器对象:
- list
- dict
- set
- class instance
而不会扫描:
- int
- str
- float
因为它们不可能形成循环引用。
五、del 与 GC 的协同关系
del 的作用可以理解为 触发引用计数减少:
del x
内部逻辑:
x 引用计数 -1
可能产生三种结果:
| 情况 | 结果 |
|---|---|
| ref > 0 | 对象继续存在 |
| ref = 0 | 立即释放 |
| 循环引用 | 等待 GC |
六、del的高级用法
1 删除列表元素
a = [1,2,3,4]
del a[1]
结果:
[1,3,4]
本质:
调用 list 的 C API 删除元素
2 删除切片
a = [1,2,3,4,5]
del a[1:3]
结果:
[1,4,5]
3 删除字典键
d = {"a":1,"b":2}
del d["a"]
4 删除对象属性
class A:
pass
obj = A()
obj.x = 10
del obj.x
5 删除变量
del x
之后访问:
NameError
七、del vs None
很多人混淆:
del x
和
x = None
区别:
| 操作 | 含义 |
|---|---|
del x | 删除变量 |
x = None | 变量仍存在,但指向 None |
示例:
x = [1,2,3]
x = None
引用变化:
[1,2,3] refcount -1
但变量 x 仍然存在。
而:
del x
变量也不存在。
八、GC模块调试
查看GC状态:
import gc
gc.get_count()
查看阈值:
gc.get_threshold()
手动触发:
gc.collect()
关闭GC:
gc.disable()
九、底层实现(CPython)
在 CPython 中:
每个对象头部包含:
PyObject
{
ob_refcnt
ob_type
}
引用计数:
ob_refcnt
宏操作:
Py_INCREF(obj)
Py_DECREF(obj)
当:
Py_DECREF -> 0
调用:
tp_dealloc
释放对象。
十、性能与最佳实践
1 不要滥用 del
Python 会自动 GC。
只有在以下情况使用:
- 大对象
- 大数组
- 大缓存
示例:
del big_dataframe
2 避免循环引用
推荐:
weakref
模块:
import weakref
3 使用上下文管理
with open("file.txt") as f:
data = f.read()
资源自动释放。
十一、经典面试题总结
Q1:del 是否删除对象?
答:
不是
删除的是 引用绑定。
Q2:Python为什么需要GC?
因为:
引用计数无法处理循环引用
Q3:什么时候对象被释放?
refcount == 0
或
GC发现循环引用
十二、一句话总结
del + 引用计数 + 分代GC 构成了 Python 的完整内存管理体系:
del → 减少引用
引用计数 → 立即回收
GC → 解决循环引用
如果你愿意,我可以继续深入讲三个更高级但很少有人真正讲清楚的内容:
1️⃣ Python GC 三代算法源码解析(90%工程师不懂)
2️⃣ 为什么 Python 的 GC 比 Java GC 简单得多
3️⃣ __del__ 析构函数导致的 GC 灾难(真实生产问题)