对于希望继续使用 “in-memory” 引擎作为默认引擎的用户,可以通过设置 engine affinity 来实现。
`lf = pl.LazyFrame({“k”: [2, 1, 0], “v”: [“a”, “b”, “c”]}) other = pl.LazyFrame({“k”: [0, 1, 2], “r”: [“x”, “y”, “z”]})
2.0: engine=“auto” 现在会解析为 streaming 引擎。
join、group_by、unpivot 等操作的行顺序不再有保证。
( lf .join(other, on=“k”, how=“left”) .collect() )
┌─────┬─────┬─────┐
│ k ┆ v ┆ r │ <- 顺序可能与 lf 的原始行顺序不一致
└─────┴─────┴─────┘
针对此查询选择可观察顺序:
( lf .join(other, on=“k”, how=“left”, maintain_order=“left”) .collect() )
或者在进程范围内将旧的 in-memory 引擎设为默认:
pl.Config.set_engine_affinity(“in-memory”)
…或者按查询设置:
( lf .join(other, on=“k”, how=“left”) .collect(engine=“in-memory”) )`
更严格的 Polars
Polars 的目标是严格并快速失败(fail fast)。错误最好在一开始就抛出,而不是在管道运行 20 分钟后才出现。针对数据不匹配的隐式行为应当是可选的(opt-in),而不是默认行为,因为这些不匹配可能隐藏 bug。随着 AI 驱动开发的兴起,这种严格性变得更有价值。Agent 可以通过调用 collect_schema() 尽早验证查询的结构——该调用会解析类型并捕获 schema 级别的不匹配,而无需实际物化任何数据。这确保了 Agent 能获得快速反馈,从而更快地迭代。并非所有错误都能在查询计划的编译阶段被捕获,有些错误依赖于数据。在这些情况下,Polars 默认采用更严格的行为,以确保能发现不一致性,而不是悄悄产出不同的结果。
以下是 Polars 变得更严格的几个示例:
如果你在不同数据类型上运行 is_in 表达式,Polars 过去会把两者转换为它们的共同超类型(supertype),即使这种转换是有损的。
下面是一个用户 ID 示例,展示静默的数据类型不匹配可能如何出错。
`# 检查某个用户 ID 是否匹配“被标记”账户 ID 列表
(flagged_ids 从 JSON 导出中加载,其中大 ID 变成了浮点数)
flagged_ids = pl.Series([9007199254740992.0]) user_id = pl.Series([9007199254740993]) # Int64 -> 一个不同的 ID,相差 1 user_id.is_in(flagged_ids)`
在 2.0 之前,user_id 会被强制转换为 Float64 以匹配 flagged_ids。但 9007199254740993 超过了 2^53(9007199254740992),即 float64 能精确表示的最大整数,因此它被静默地向下舍入为 9007199254740992.0,产生了一个误报(false positive)。
在 2.0 中这会直接报错:InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data.,用户应显式地进行类型转换来处理有损的类型转换。
水平拼接(horizontal concat)现在会检查长度,而不是静默地用 null 填充。
`# 将每日交易计数与每日欺诈标记计数拼接, transactions = pl.DataFrame({“day”: [1, 2, 3, 4, 5], “count”: [120, 98, 143, 87, 156]})
上游任务在第 5 天静默失败
fraud_flags = pl.DataFrame({“flagged”: [2, 0, 5, 1]}) # 只有 4 行
pl.concat([transactions, fraud_flags], how=“horizontal”)`
shape: (5, 2)
┌─────┬───────┬─────────┐
│ day ┆ count ┆ flagged │
│ 1 ┆ 120 ┆ 2 │
│ 2 ┆ 98 ┆ 0 │
│ 3 ┆ 143 ┆ 5 │
│ 4 ┆ 87 ┆ 1 │
│ 5 ┆ 156 ┆ null │ <- 第 5 天静默地没有标记计数
└─────┴───────┴─────────┘
在 2.0 中,这将抛出:
ShapeError: cannot concat dataframes with different heights in 'strict' mode
如果你想要的就是填充,必须通过 how="horizontal_extend" 显式选择该行为,向读者清楚地表明你的意图。
另一个值得一提的变化是移除了许多含糊不清、或本应通过专用解析表达式完成的类型转换,从而让数据解析只有一种显而易见的方式。
`pl.Series([None, 1, 0, 2], dtype=pl.UInt32).cast(pl.Enum([“a”, “b”, “c”]))
ComputeError: casting from u32 to enum is not supported.`
int → categorical 请改用:.cat.to(dtype),categorical → int 请改用:.cat.physical()。
`pl.Series([“2022-08-30”]).cast(pl.Date)
InvalidOperationError: casting from string to date is not supported.`
请改用:.str.to_date() / .str.to_datetime()。这些方法允许你指定解析格式,让你能更好地控制数据的解析方式。
这些只是几个示例,我们还落地了更多严格性改进。完整内容请参阅迁移指南。
我们投入了大量精力,确保你作为用户或你的 Agent 在使用了已不再支持的旧参数时仍能继续运行。为此我们新增了两个类型化异常:polars.exceptions.AttributeRemovedError 和 polars.exceptions.ArgumentRemovedError,分别处理被移除的属性和方法,以及被移除的参数。
错误信息应当会指引你使用新的 API。下面我们展示两个示例。