Python技术迷

Python 中如何管理内存?

跑路的老板可以一走了之,但程序猿写的代码和吃掉的内存,一点都跑不掉。今天咱们来聊聊一个让无数 Python 工程师默默掉头发的事儿——内存管理。

老话说得好,“Python 用起来爽,跑久了就膨胀”,你以为自己写的是一只轻盈的小猫,结果运行一段时间后它摇身一变成了大肥虎,占着内存不撒手。这事儿到底怪谁呢?怪解释器?怪代码?还是怪你喜欢无限循环套无限递归?

我觉得,得先把锅分清楚,然后才好下锅炒。

Python 到底怎么管内存的?

先说正经的,Python 不是那种你 new 一个对象出来就得手动释放的语言,它有自己的内存管理系统。简单来说,它靠两大法宝:

  1. 引用计数(Reference Counting)
  2. 垃圾回收(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 管理内存这事儿,本来就不是让你背诵的技术八股文,而是日常开发中活生生踩过的坑。写代码时多点心眼,内存自然不会爆。程序员的头发还能再多活两年。你说是不是?