pl.scan_csv()是百亿级CSV数据唯一可行入口,因pl.read_csv()会全量加载致OOM;必须显式指定dtypes、列名和low_memory=True等参数,并用惰性API链式操作后collect()执行。

pl.scan_csv() 是百亿级数据唯一能跑通的入口,pl.read_csv() 在这种规模下直接 OOM。这不是“建议换”,而是物理层面不可行。
必须用 scan_csv 而不是 read_csv
百亿行 CSV 解压后常超 100GB。pl.read_csv() 会全量加载进内存,哪怕你只 select 一列;pl.scan_csv() 不读数据,只建执行计划,后续 filter、select、group_by 都被下推到读取阶段——真正只加载需要的块和列。
- 不指定
dtypes会导致 Polars 扫描全量样本推断类型,启动慢且不准;务必显式传入{"id": pl.UInt64, "ts": pl.Datetime} - 有 header 但列名含空格或特殊字符时,别依赖
infer_schema_length,用new_columns=["col_a", "col_b"]重命名 - 加
low_memory=True启用流式解析,避免临时缓冲区膨胀;配合row_count_name="idx"可省掉后续with_row_count()的额外遍历
filter 和 group_by 写法直接影响性能
Pandas 的 .query("x > 100") 或布尔索引是 Python 层逐行判断;Polars 的 filter(pl.col("x") > 100) 是编译成 Rust 指令的向量化操作,无解释器开销。
- 字符串过滤优先用
str.starts_with()或str.ends_with(),它们能触发 SIMD 加速;str.contains()是 fallback 路径,慢得多 -
group_by("key").agg()默认走哈希分组;若 key 已排序且想保持顺序,加maintain_order=True可跳过重排 - 聚合时避免
agg(pl.col("x").apply(...))——这会退化为 Python 循环;所有计算必须用内置表达式,如pl.mean("x")、pl.max("y")
collect() 时机决定内存峰值
惰性模式下,.filter().select().group_by().agg() 都不触发计算,直到调用 .collect() 才真正执行并返回 DataFrame。这个“收口”点必须手动控制。
- 中间结果不要反复
.collect(),比如先df1 = q.collect()再df2 = df1.filter().collect()——这会让两份数据同时驻留内存 - 调试时可用
.explain()看物理执行计划,确认filter是否被下推、是否用了并行扫描 - 如果最终要写 Parquet,直接链式调用
.collect().write_parquet("out.parq"),别拆成两步
Python 3.12 下的兼容细节
Polars 1.35.1 官方支持 Python 3.12,但部分旧插件(如某些自定义 pl.plugins)可能未适配。关键不是版本数字本身,而是底层 Arrow 和 Rust 运行时是否匹配。
立即学习“Python免费学习笔记(深入)”;
- 安装时用
pip install "polars[all]",确保包含 PyArrow、cloud storage 等可选依赖 - 避免混用
pd.DataFrame和pl.DataFrame做频繁转换——pl.from_pandas()有零拷贝路径,但df.to_pandas()会强制 materialize 全量数据 - 在 Jupyter 中,
pl.Config.set_fmt_str_lengths(100)可防止长字符串截断影响调试,但别在生产 pipeline 里设
scan_csv 时就想清楚:哪几列真需要?哪些条件能下推?聚合是否可合并?漏掉任何一个,都可能让内存曲线突然拉直。


















