商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Python怎样避免在循环中不断追加数据_预分配列表收集后统一转换为DF

Python怎样避免在循环中不断追加数据_预分配列表收集后统一转换为DF

  发布于2026-07-20 阅读(0)

扫一扫,手机访问

这段代码揭示了一个很经典的问题——在循环里不断用 append() 往列表里塞东西,跑着跑着突然变慢,尤其是在处理几万甚至几十万行数据的时候。很多人都遇到过这个场景,第一反应是“是不是 append() 本身效率有问题?”但从底层实现来看,真正的原因可能和你想的不太一样。

Python怎样避免在循环中不断追加数据_预分配列表收集后统一转换为DF

为什么循环中 append() 会变慢?

关键问题不在于 append() 这个操作本身有多慢,而是 Python 列表在容量不足时,会触发一次扩容机制。CPython 的实现里,每次扩容大约是 1.125 倍——意思是当当前容量不够用了,解释器会重新申请一块更大的内存空间,然后把旧元素一个个拷贝过去。假设你循环了几万次,扩容可能发生几十次,每次拷贝都伴随着内存分配的开销。累计下来的耗时,早就不是线性增长那么简单了。

还有一个更隐蔽的陷阱:如果循环体里还顺手做了字符串拼接、字典构造、或者临时对象的创建,这些操作会显著增加垃圾回收的压力。GC 被频繁唤醒,整体执行节奏自然就被拖慢了。

预分配列表的两种可靠做法

预分配的前提是你得提前知道最终要装多少东西。如果能确定行数——比如遍历一个固定长度的 rangezip、或者读一个已知行数的 CSV——直接上 [None] * n 是最省事的做法。如果长度未知但逻辑上可以推导出来,列表推导式是更好的选择,解释器会在内部自动优化成一次预分配加逐行填充,比手动 append() 快得多,也更符合 Python 的风格。

  • 已知长度 nresults = [None] * n,然后 results[i] = (x, y, z) 直接按索引赋值
  • 逻辑明确但长度未知:直接上列表推导式,比如 [(f(x), g(x)) for x in data]
  • 特别提醒:别用 [[0] * n] 这种写法——它生成的其实是同一个子列表的多个引用,你改一个,其他全跟着变,后果可想而知

从预分配列表到 DataFrame 的高效转换

pd.DataFrame() 这个构造函数有“脾气”,不同输入结构的性能表现天差地别。tuple 列表或 list 列表速度最快;如果传的是 dict 列表,pandas 需要额外做字段对齐,开销就上去了;要是用 pd.concat 拼接多个 Series,性能会断崖式下跌。

  • 推荐的做法:预分配成 list[tuple]list[list],并且保证列的顺序与目标 DataFrame 的列名严格一致
  • 构造时显式指定 columns=,否则 pandas 在遇到混合类型数据时容易自动推成 object,后续计算容易出问题
  • 如果原始数据里混了一堆 Nonenp.nan,建议构造完后立刻用 df.astype(...) 显式设定 dtype,防止后续操作意外把整列升格成 object
data = [None] * len(items)for i, item in enumerate(items):    data[i] = (item.id, item.name, item.score)df = pd.DataFrame(data, columns=['id', 'name', 'score'])

什么情况下不该预分配?

预分配不是万能的。如果你的循环逻辑里需要动态判断“要不要追加”——比如过滤、异常跳过、嵌套条件——硬套预分配只会让代码变得又难读又容易出错。你得额外维护一个计数器,处理那些没填数据的空位,最后还得手动切片去掉 None。代价太大了。

这种情况下,更务实的做法是老老实实用 append(),但把构造 DataFrame 的动作拖到循环结束之后一次性完成。关键是要确保收集到的数据结构统一、类型稳定,别搞出“dict 里面嵌套 list 里面再套 tuple”这种混乱结构。

说实话,真正影响性能的从来不是 append 这一行代码,而是数据结构混乱、类型不稳定、以及多余的中间转换。与其死盯着预分配不放,不如先检查一下你的 data 列表里每个元素是不是长得一模一样。

本文转载于:https://www.php.cn/faq/2322965.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注