Python 中如何管理内存?
跑路的老板可以一走了之,但程序猿写的代码和吃掉的内存,一点都跑不掉。今天咱们来聊聊一个让无数 Python 工程师默默掉头发的事儿——内存管理。
老话说得好,“Python 用起来爽,跑久了就膨胀”,你以为自己写的是一只轻盈的小猫,结果运行一段时间后它摇身一变成了大肥虎,占着内存不撒手。这事儿到底怪谁呢?怪解释器?怪代码?还是怪你喜欢无限循环套无限递归?
我觉得,得先把锅分清楚,然后才好下锅炒。
Python 到底怎么管内存的?
先说正经的,Python 不是那种你 new 一个对象出来就得手动释放的语言,它有自己的内存管理系统。简单来说,它靠两大法宝:
引用计数(Reference Counting) 垃圾回收(Garbage Collection)
这俩配合得跟“双人舞”一样——不过跳得不好看时,也能踩你一脚。
引用计数:我是谁,谁用了我,我就给谁记一笔
Python 中每个对象都有一个“引用计数器”,你每次把对象赋给一个变量,这个计数器就 +1,变量销毁就 -1。
举个栗子👇
import sys
a = []
print(sys.getrefcount(a)) # 输出可能是 2:a 和 getrefcount 参数本身都引用了这个对象
b = a
print(sys.getrefcount(a)) # 输出变成 3
只要这个计数大于 0,Python 就不会把这个对象干掉。只有当它变成 0 的时候,Python 才会把它清理出内存。
听起来挺合理对吧?但你以为 Python 就靠这个就能高枕无忧了?too young。
垃圾回收:擦屁股队伍专收“死循环”
引用计数有个致命的问题:循环引用。比如你定义两个对象互相引用对方,但你自己却不再需要它们了,这时候它们的引用计数都不为 0,但其实已经是“垃圾”了。
classA:
def__init__(self):
self.b = None
classB:
def__init__(self):
self.a = None
a = A()
b = B()
a.b = b
b.a = a
然后你:
a = None
b = None
你以为清理了,其实是把它们甩进了一个“没人理的角落”。
这时候就得靠 Python 的 垃圾回收器(GC) 出马了。Python 有一个模块叫 gc,它会定期扫描对象图,发现那些被相互引用、但整体无法被访问的对象,然后统一处理。
你甚至可以手动开启一波清理:
import gc
gc.collect()
小对象和大对象,Python 分得可清楚了
Python 内部对内存也挺“门清”,它会把对象按大小分成两类:
小对象(通常小于 512 字节):从一个叫 pymalloc 的池子里拿 大对象:直接找操作系统要内存
这就是为什么你可能频繁创建 int、str、list 之类的小对象也没见内存飙得太猛,因为 Python 把它们都提前准备好了,创建和销毁都很快。
但你要是频繁创建大数据对象,比如超大的字典、DataFrame,这就很容易让你的内存一路狂飙。怪不得很多人跑着跑着脚本,就发现电脑风扇起飞了——内存飙上天,Python 正在“拼了老命存数据”。
Python 的“缓存”套路,你知道几个?
Python 其实偷偷做了不少缓存,尤其是对小整数和字符串这种经常用的小玩意儿。
a = 100
b = 100
print(a is b) # True,小整数缓存
a = "hello"
b = "hello"
print(a is b) # True,字符串驻留
这就说明,不是你每次写个 a = 100,它都新建一个对象,Python 背地里已经给你缓存好了。
当然你要写:
a = 1000
b = 1000
print(a is b) # False
就不一样了,大整数就不进那个小缓存池,得新造。
怎么才能不让内存溢得满天飞?
说白了,内存管理的核心就是——及时释放你不再用的资源。但话说回来,Python 已经帮我们做了 80%,我们只需要别太浪了就行。
我平时会注意这些点:
别滥用全局变量,很多人习惯一开文件就丢全局里,一直也不关,怪谁? 用完的大对象显式清空,比如:
df = None
对象不用时主动 del掉,尤其是那些一看就很大的,比如爬虫下来的页面内容定期 gc.collect(),特别是做长期运行的脚本或服务时如果是处理大量数据,推荐用 generator而不是直接返回 list用工具辅助,比如 objgraph和memory_profiler看看到底谁在吃内存
最后,这题要是你在面试里被问到了,应该怎么答?
面试官问:Python 是怎么管理内存的?你该怎么回答?
最优答案可以这么说👇
Python 主要通过引用计数机制进行内存管理,每个对象都有一个引用计数器,计数为 0 时就会被回收。为了处理循环引用,Python 还内置了垃圾回收器(gc 模块),使用代际回收策略定期扫描不可达对象并释放内存。此外,Python 对小对象使用 pymalloc 实现快速分配和复用,大对象则直接向操作系统申请。同时,为了提高效率,Python 还对小整数和常用字符串等对象进行了缓存处理。开发过程中,我们可以通过合理控制变量生命周期、使用生成器、清空不必要的引用以及手动触发
gc.collect()来优化内存使用。
当然,最后你还得微微一笑,加上一句:
“但真要跑大数据场景,我还是建议上 PyPy 或者 JIT 编译器,不然靠 CPython 跑很容易 OOM(内存溢出)。”
你这么一说,面试官就知道你不仅懂原理,还踩过坑,分分钟加分。
说到底,Python 管理内存这事儿,本来就不是让你背诵的技术八股文,而是日常开发中活生生踩过的坑。写代码时多点心眼,内存自然不会爆。程序员的头发还能再多活两年。你说是不是?