列表推导式一用就爽?大数据量下它把我服务器内存榨干了,原来生成器才是yyds

简介: 本文通过一次线上内存告警事故,生动揭示列表推导式与生成器表达式的本质差异:前者一次性加载全部数据,易致内存爆满;后者惰性求值、逐项处理,内存占用极低。用真实案例与数据对比,阐明何时该用生成器——大文件、数据流、无限序列等场景下,它是保命利器。(239字)

那天,服务器报警了

周五下午三点半,我正准备收拾东西下班。

运维同事在群里发了一条消息,配了一张截图:“线上内存使用率95%,你们谁在跑什么任务?”

我后背一凉。今天确实部署了一个新脚本,用来处理上个月的用户行为日志。

文件不大,也就2.8GB。我心想,用列表推导式把数据读进来过滤一下,Python嘛,写起来多优雅:

logs = [line for line in open('user_logs.txt') if 'error' in line]

就这一行。优雅,简洁,Pythonic。

然后内存就爆了。

我盯着屏幕上那行代码,不敢相信问题出在这里。2.8GB的文件,这行代码直接给我干出了4.2GB的内存占用。服务器开始疯狂swap,磁盘IO飙到100%,整个服务都跟着抖动。

一个小时后,我把这行代码改成了一行生成器:

logs = (line for line in open('user_logs.txt') if 'error' in line)

内存占用降到了不到50MB。速度反而还快了。

那天我下班的时候,天已经黑了。但有一件事我彻底搞明白了:列表推导式和生成器表达式,看起来就差一个括号,实际上是天壤之别。

代理 IP 使用小技巧 让你的数据抓取效率翻倍 (52).png

列表推导式到底做了什么?

先来看看列表推导式的工作原理。

当Python执行这行代码时:

squares = [x**2 for x in range(10000000)]

背后发生的是:

  1. Python先创建一个空列表
  2. 开始循环,每次计算 x**2
  3. 把计算结果一个一个塞进列表里
  4. 直到循环结束

这个过程看起来没问题对不对?问题就在于第3步——所有计算结果都被存进了内存

1000万个整数,每个整数28个字节(Python的int对象开销很大),再加上列表本身的空间——280MB就这样没了。

这就是列表推导式的“原罪”:它一次性把所有结果全部生成并存储

数据量小的时候,这根本不是问题。但数据量一大,它就像一台内存抽水机,把可用内存榨得干干净净。

列表推导式的内存消耗有多夸张?

我做个测试给你看。

读取一个1GB的日志文件,过滤出包含"ERROR"的行:

# 列表推导式
lines = [line for line in open('log.txt') if 'ERROR' in line]

运行过程中,内存占用峰值是多少?

3.2GB

为什么会比文件本身还大?因为Python把每一行都拆成了独立的字符串对象,每个字符串都有额外的对象头开销(大约49个字节)。文件里的原始字节变成了Python对象,内存占用直接膨胀了好几倍。

如果用普通for循环加append,结果是一样的:

lines = []
for line in open('log.txt'):
   if 'ERROR' in line:
       lines.append(line)

内存占用依然是3.2GB。列表推导式只是写法更简洁,底层的本质没变——都是把结果全部装进列表

生成器到底做了什么?

再看生成器表达式的写法:

lines = (line for line in open('log.txt') if 'ERROR' in line)

运行这一行,内存占用几乎为0——准确地说,几十KB。

为什么?因为这一行代码什么都没算

它只是创建了一个生成器对象。这个对象记住了三件事:

  • 你要遍历哪个可迭代对象(open('log.txt')
  • 你要执行什么操作(line for line ... if 'ERROR' in line
  • 现在遍历到什么位置了(还没开始)

当你真正遍历它的时候:

for line in lines:
   process(line)

生成器才开始工作:读一行,处理一行,处理完就丢掉,再读下一行。

内存里始终只有一行数据

这就是生成器的核心思想:惰性求值——需要的时候才计算,算完就扔,绝不囤货。

不是说生成器一定更快

这里要澄清一个常见的误解:生成器不一定比列表推导式快

事实上,在大多数场景下,列表推导式更快

为什么?因为列表推导式在C语言层面做了优化,循环速度比Python的for循环要快。而且一次性分配好内存,比不断地申请释放内存效率更高。

我做了一个简单的性能测试:

场景1:计算1000万个数的平方

方式 耗时 内存占用
列表推导式 0.47秒 280MB
生成器表达式 0.52秒 几乎为0

列表推导式快了大约**10%**。

场景2:处理一个2.8GB的日志文件

方式 耗时 内存占用 结果
列表推导式 12.3秒 4.2GB 内存溢出
生成器表达式 11.8秒 48MB 正常完成

看到区别没有?当数据量小到内存装得下的时候,列表推导式确实快。但当数据量大到内存装不下的时候——快有什么用?直接崩了

所以选择的标准很简单:

  • 数据量小,内存随便装 → 列表推导式,简洁又快
  • 数据量大,内存吃紧 → 生成器,保命要紧

什么时候该用生成器?

判断标准就一条:你是否需要一次性持有所有数据

不需要,就用生成器。

下面这些场景,强烈建议用生成器

1. 处理大文件

# 错误写法
lines = [line.strip() for line in open('huge_file.csv')]

# 正确写法
lines = (line.strip() for line in open('huge_file.csv'))

2. 数据流处理

# 从API分页获取数据
def fetch_all_users():
   page = 1
   while True:
       data = requests.get(f'/users?page={page}')
       if not data:
           break
       for user in data:
           yield user
       page += 1

# 使用
for user in fetch_all_users():
   process(user)  # 边取边处理,不会把所有用户都存内存里

3. 无限序列

# 生成无限斐波那契数列
def fibonacci():
   a, b = 0, 1
   while True:
       yield a
       a, b = b, a + b

# 只取前100个
for num in fibonacci():
   if num > 100:
       break
   print(num)

如果用列表,你根本没法表示无限序列——内存会炸。

生成器不只有这一种写法

除了生成器表达式(圆括号那个),Python还有两种方式创建生成器:

方式一:yield关键字

def read_large_file(path):
   with open(path) as f:
       for line in f:
           yield line  # 每次返回一行

任何包含 yield 的函数,调用时返回的都是生成器对象。

方式二:生成器表达式

squares = (x**2 for x in range(1000000))

就是前面讲的圆括号写法。

两种方式选哪个?逻辑简单用表达式,逻辑复杂用函数。

进阶:链式生成器

生成器还有一个特别酷的用法——链式组合

你可以把多个生成器串起来,每个只做一件事,清晰又高效:

def read_lines(path):
   with open(path) as f:
       for line in f:
           yield line

def filter_error(lines):
   for line in lines:
       if 'ERROR' in line:
           yield line

def extract_timestamp(lines):
   for line in lines:
       yield line.split('|')[0]  # 假设时间戳在第一列

# 组合使用
lines = read_lines('log.txt')
errors = filter_error(lines)
timestamps = extract_timestamp(errors)

for ts in timestamps:
   print(ts)  # 每一步都是流式处理,内存占用始终最小

每一步都是惰性的,数据像流水一样经过一个个处理环节,内存里永远只有一条数据。

还有两种推导式也同理

列表推导式有内存问题,那字典推导式和集合推导式呢?

一样的。

# 字典推导式——一次性生成全部
user_map = {u.id: u for u in get_all_users()}  # 如果用户有百万级,内存直接爆炸

# 改为生成器配合字典构造
user_map = dict((u.id, u) for u in get_all_users())  # 稍微好一点,但还是存了全部

但注意:字典和集合本质上就是要存全部数据的。如果你需要字典或集合,那就没办法,内存占用是必须的。这时候能优化的是数据源部分——用生成器逐个产出数据,避免数据源本身再占一份内存。

一个实战中的取舍

回到开头那个故事。

2.8GB的日志文件,我后来是怎么处理的?

def process_logs(file_path):
   with open(file_path) as f:
       # 生成器表达式:逐行读取
       error_lines = (line for line in f if 'ERROR' in line)
       
       # 生成器函数:解析每行
       parsed = (parse_line(line) for line in error_lines)
       
       # 又一个生成器:只取需要的时间段
       filtered = (item for item in parsed if item['timestamp'] > cutoff)
       
       # 最后汇总统计
       stats = {}
       for item in filtered:
           stats[item['type']] = stats.get(item['type'], 0) + 1
           
       return stats

整个流程中,内存里最多同时存在几行数据。2.8GB的文件处理完,内存峰值不到100MB

服务器稳了,我也稳了。

一句话总结

列表推导式:爽,但贪婪。一次吃光所有数据。

生成器表达式:克制,但持久。吃一口消化一口。

数据小的时候,随便你用哪个,代码好看更重要。数据大的时候——

用生成器,是程序员最后的体面。

别让你的服务器,为一行代码殉情。

目录
相关文章
|
6天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2027 9
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
6天前
|
云安全 人工智能 安全
|
7天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max-Preview深度全解析:2.4万亿参数旗舰MoE模型+Token Plan限时优惠完整落地指南
2026年7月,全新旗舰级混合专家大模型Qwen3.8-Max-Preview正式开放抢先体验,作为通义千问Qwen3系列规格最高、综合推理能力顶尖的新一代模型,该模型总参数量达到2.4万亿(2.4T),是当前线上可调用的原生多模态旗舰模型,综合推理水准对标海外顶级Fable 5模型,在复杂工程开发、长文档深度分析、多步骤智能体自治、跨境多语言创作、海量数据挖掘五大高难度业务场景实现跨越式性能提升。
877 1
|
7天前
|
人工智能 自然语言处理 数据挖掘
最新版通义千问(Qwen3.8-Max-Preview)功能介绍
2026年,通义千问正式推出全新旗舰级大模型 **Qwen3.8-Max-Preview 预览版**,作为首款突破万亿参数规格的新一代基座模型,该模型总参数量达到**2.4万亿**,采用全新迭代的MoE混合专家架构,综合推理性能、长文本处理、多模态理解、复杂任务规划能力全面超越前代Qwen3.7-Max版本,整体实力跻身全球第一梯队,可对标海外顶级旗舰模型,是当前面向复杂工程开发、多智能体协同、超长文档解析、专业办公自动化场景的最优国产基座模型。
884 0
|
8天前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
881 37
|
5天前
|
自然语言处理 测试技术 API
通义千问Qwen3.8-Max-Preview全功能解析:2.4万亿参数旗舰模型深度使用指南
在大模型技术持续迭代的当下,通义千问推出的Qwen3.8-Max-Preview作为新一代旗舰预览版模型,凭借2.4万亿参数的超大规模、多模态融合能力与全场景适配特性,成为开发者与企业用户探索AI应用的核心工具。该模型采用稀疏混合专家(MoE)架构,是通义千问首个突破万亿参数的多模态模型,可同时处理文本、图像、视频与文档等多种数据形态,在全栈代码开发、复杂逻辑推理、长文档分析与多智能体协作等场景实现跨越式升级。本文将全面拆解Qwen3.8-Max-Preview的核心功能,详解API调用流程与配置方法,覆盖多场景实战技巧,帮助用户快速掌握这款旗舰模型的使用方法,充分释放其性能潜力。
427 1
|
8天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max 预览版全解析:2.4 万亿参数旗舰模型,Token Plan 限时优惠指南
Qwen3.8-Max-Preview是通义千问Qwen3系列旗舰MoE大模型,参数达2.4万亿,综合推理能力居行业第一梯队。支持思考/快速双模式,擅长大模型五大高难场景。现于阿里云百炼Token Plan、Qoder及QoderWork上线体验,个人版低至39元/月。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
652 1
Qwen3.8-Max 预览版全解析:2.4 万亿参数旗舰模型,Token Plan 限时优惠指南