循环中频繁创建对象会拖慢Python程序,因每次调用list()、dict()或MyClass()均需分配内存、执行__init__并可能触发GC,在百万级循环中显著增加CPU和内存压力。

为什么循环里频繁创建对象会拖慢 Python 程序
因为每次 list()、dict()、MyClass() 都要分配内存、调用 __init__、可能触发 GC,尤其在百万级循环中,对象生命周期短但创建开销大,CPU 和内存压力会明显上升。不是所有对象都能被优化,但凡构造逻辑固定、状态可复用、或只需“一次生成多次消费”的场景,就值得考虑迭代器替代。
用 yield 替代 return list:最直接的流式改造
常见错误是写一个函数返回完整列表,再在 for 循环里遍历——这等于把全部数据先装进内存;改成生成器后,每次只产出一个项,上游消费者拿一个算一个。
def load_records_bad(filename):
return [parse_line(line) for line in open(filename)] # 全量加载
<p>def load_records_good(filename):
with open(filename) as f:
for line in f:
yield parse_line(line) # 每次只产出一个 record-
load_records_good不会因文件过大而 OOM,适合日志、CSV、JSONL 等流式文件 - 必须用
with open手动管理文件句柄,否则yield会让文件对象长期悬空 - 不能对生成器做
len()或索引访问,如需随机访问,得转成list——但那就退化回原问题了
复用对象实例:避免在循环内重复 re.compile 或 datetime.strptime
正则编译、日期解析、JSON 解析器初始化等操作开销固定但显著,放在循环外能省下 90%+ 的 CPU 时间。
import re
from datetime import datetime
<h1>❌ 错误:每次循环都重新编译</h1><p>for line in lines:
m = re.match(r'(\d{4})-(\d{2})', line) # 每次都调用 re.compile 内部逻辑</p><h1>✅ 正确:预编译 + 复用</h1><p>PATTERN = re.compile(r'(\d{4})-(\d{2})')
for line in lines:
m = PATTERN.match(line) # 直接 match,无编译开销</p><h1>同理,strptime 也建议缓存格式字符串,或改用更轻量的 <code>dateutil.parser.parse</code>(仅当格式不固定时)-
re.compile缓存后,匹配速度提升 3–5 倍,且避免正则缓存表膨胀 -
datetime.strptime是纯 Python 实现,比datetime.fromisoformat慢一个数量级,固定格式优先用后者 - 如果对象本身带状态(比如自定义 parser),确保它是线程安全或不跨迭代共享状态
用 itertools 组合已有迭代器,避免中间 list
像 filter、map、itertools.chain 这些返回的是惰性迭代器,而 list(filter(...)) 会立刻执行并吃内存。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
from itertools import chain, islice <h1>❌ 把三组数据全展开再拼</h1><p>all_items = list(chain(list_a, list_b, list_c))</p><h1>✅ 流式拼接,下游消费多少取多少</h1><p>all_items = chain(iter_a, iter_b, iter_c) # iter_a 等本身可以是生成器</p><h1>❌ 取前 100 条却先生成全部</h1><p>top_100 = sorted(items, key=lambda x: x.score, reverse=True)[:100]</p><h1>✅ 用 heapq.nlargest,不排序全量</h1><p>from heapq import nlargest top_100 = nlargest(100, items, key=lambda x: x.score)
-
islice(gen, n)比list(gen)[:n]节省内存,尤其当n远小于总长度时 -
itertools.tee看似能“复制”迭代器,但内部会缓存已消费项,慎用于大流量场景 - 链式调用如
map(f, filter(p, gen))是安全的,每层都是惰性求值,不会产生中间 list
真实项目里最容易被忽略的,是把“对象复用”和“流式产出”混为一谈:前者解决单次构造成本,后者解决内存驻留成本。两者常需配合使用,比如一边复用 json.JSONDecoder() 实例,一边用 yield 逐行解码 JSONL。

















