Py学习  »  Python

Python内存优化实战:5个模式让内存占用降低70%

IT服务圈儿 • 4 月前 • 474 次点击  

来源丨经授权转自 数据STUDIO
者丨云朵君
你的Python程序处理大数据集时是否频繁崩溃?尝试这几个内存优化技巧,告别内存不足的烦恼。

我原本以为自己对 Python 内存管理了如指掌。

直到我的应用程序每次尝试处理大型数据集时,都像廉价折叠椅一样崩溃——我才意识到问题的严重性。

经过几周深度调试、堆分析和近乎偏执的重构后,我的内存使用量下降了 70% 。代码运行更快、更精简,面对 GB 级数据时不再需要呼救。

以下是真正产生效果的 5 个模式。

1. 流式处理代替一次性加载

让我们从这个人人都“知道”但很少实际“做到”的明显技巧开始:停止将巨型文件加载到内存中。

低效的做法:

# 一次性将整个大文件读入内存
with open('huge.csv'as f:
    data = f.readlines()   # 这将耗尽你的RAM

更好的方式:

def read_in_chunks(file_path, chunk_size=1024*1024):
    """按块读取文件,避免一次性加载"""
    with open(file_path, 'rb'as f:
        while chunk := f.read(chunk_size):
            yield chunk  # 每次只返回一个数据块

# 使用生成器逐块处理文件
for chunk in read_in_chunks('huge.csv'):
    process(chunk)

逐行读取与一次性读取 1GB 文件相比,可以将峰值内存从千兆字节减少到 仅几兆字节

仅这一项改变就让我能在 8GB RAM 的笔记本电脑上处理 7GB 的日志文件,而无需交换内存。

2. 生成器管道代替列表

你是否曾经构建了一个庞大的列表,然后只循环一次?这就像你只想吃点零食,却买了整个自助餐。

列表方式(内存消耗大):

# 立即创建包含100万个元素的列表
items = [expensive_function(x) for x in range(1_000_000)]
for item in items:
    do_something(item)

生成器方式(内存友好):

# 使用生成器表达式,按需计算元素
items = (expensive_function(x) for x in range(1_000_000))
for item in items:
    do_something(item)

我的一条数据流水线仅通过将列表推导式替换为生成器表达式,峰值 RAM 就从 3.4GB 降至 280MB。

生成器表达式的核心优势是 惰性计算,只在需要时生成下一个元素,不提前创建完整序列。与列表推导式相比,生成器在处理大型数据集时可以显著减少内存占用,但需要注意生成器是“一次性”的,遍历结束后即耗尽。

3. 使用 __slots__ 创建更精简的对象

Python 对象默认将其属性存储在字典中 —— 这很灵活,但很耗内存。如果你有数千(或数百万)个实例,那就是浪费空间。

普通类:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

精简版类:




    
class Point:
    __slots__ = ('x''y')  # 显式声明允许的属性
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

对于 1000 万个小型对象,__slots__ 为我节省了约 500MB 的内存。对于一行代码来说,这效果还不错。

Python 普通类的实例属性存储在 __dict__ 字典中,这种设计虽然灵活,但每个实例都需要维护一个字典,在对象数量大时会累积成可观的内存消耗。

当使用 __slots__ 时,Python 会在类级别创建一个固定的内存布局,类似 C 语言中的结构体,不再为每个实例创建 __dict__,而是将属性直接存储在预分配的固定大小数组中。

实验表明,使用 __slots__ 后,**内存占用降低约 46.7% 对象创建时间提升 37.5%属性访问速度提升 4.8%**。

需要注意的是,使用 __slots__ 的类实例将无法动态添加新属性,这实际上定义了一个隐式接口契约,明确告诉其他开发者这个类有哪些属性。

4. 避免在循环中创建临时对象

Python 会很乐意整天创建临时对象 —— 而你的内存将为此付出代价。这一点很隐蔽,因为它常常隐藏在显而易见的地方。

低效方式:

results = []
for row in big_dataset:
    # 创建临时对象并添加到列表
    results.append(process(row))

更内存友好的方式(使用生成器 + yield):

def process_dataset(dataset):
    """使用生成器逐个处理数据"""
    for row in dataset:
        yield process(row)  # 每次只生成一个结果

# 流式处理结果,不保存在内存中
for result in process_dataset(big_dataset):
    handle(result)

你不必将所有处理结果都保存在 RAM 中。它们在生成时就被处理,即使有大量输入,内存也能保持稳定。

5. 重用缓冲区而不是重新分配

如果你处理二进制或大型字符串数据,不断重新分配会严重损害性能和内存效率。我在解析 GB 级原始传感器日志时学到了这一点。

简单的方法:

data = b''
for chunk in stream:
    data += chunk  # 每次连接都会创建一个新对象

更好的方法:

from io import BytesIO

# 创建一个可增长的缓冲区
buffer = BytesIO()
for chunk in stream:
    buffer.write(chunk)  # 在缓冲区中追加数据
data = buffer.getvalue()  # 最后一次性获取所有数据

在一个工作负载中,这使峰值分配减少了 62% 。此外,它还防止了我的脚本因垃圾收集峰值而每隔几秒就冻结一次。

Python 的 Buffer 协议提供了一种高效访问对象内部数据内存的方式。在处理动态数据时,正确使用缓冲区可以避免不必要的数据拷贝,提升性能。

进阶思考:Python内存管理的内幕

要真正掌握内存优化,我们还需要了解一些Python内部机制:

对象缓存池机制:Python 对常用对象类型(如浮点数)实现了缓存池。当浮点数对象被销毁后,并不急着回收对象所占用的内存,而是将该对象放入一个空闲的链表(缓存池)中。后续如果需要创建新的浮点数对象时,直接从链表中取出之前放入的对象,重新初始化即可,这样就避免了内存分配造成的开销。

内存分析先行:我没有仅仅猜测这些模式 —— 而是进行了测量。memory_profiler 和 tracemalloc 成了我最好的朋友。

pip install memory_profiler
python -m memory_profiler script.py

你常常会发现 90% 的内存使用发生在 10% 的代码中 —— 修复这些热点代码比微优化所有地方更有效。PyCon 演讲也强调,先使用 tracemalloc 等工具诊断,再针对性地优化是内存优化的正确路径。

写在最后

我们回顾一下这五个让Python程序内存占用大幅降低的关键模式:

  1. 流式处理大型文件,避免一次性加载到内存
  2. 用生成器表达式代替列表推导式,实现惰性计算
  3. 为大量实例的类使用 __slots__ ,减少对象内存开销
  4. 避免在循环中累积临时对象,使用生成器逐步处理
  5. 重用缓冲区而不是重复连接,减少内存碎片和分配开销

最关键的是:在优化前先用 memory_profilertracemalloc 等工具分析内存使用,找到真正的瓶颈点。

你在处理大数据集时还用过哪些内存优化技巧?有没有遇到过特别棘手的内存泄漏问题?欢迎在评论区分享你的经验和问题!

1、写了5年多线程代码,今天才真正搞懂死锁是怎么发生的

2、IntelliJ IDEA 2026.1 EAP 3 紧急发布,AI 能力再次加强,回收站终于有了!

3、shared_ptr遇到的那些坑

4、手把手教你如何从Linux内核抄代码!

5、你严重低估了 iPhone 锁屏的作用

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/192898