Polars 2.0 候选版发布:默认启用流式引擎与更严格类型检查
Polars 2.0 首个候选版本发布
我们今日发布 Polars 2.0 的首个候选版本,正式版将在未来几周内推出。本次大版本更新并非以新增功能为目标,反而希望带给大家一种"平淡无奇"的体验。升级主版本号的核心原因,是为了彻底摒弃过去的部分设计决策——这些设计如今已成为发展阻碍,同时我们也将把默认配置调整为更合理的方案,让更多用户受益。其中最关键的默认配置变更为:所有 LazyFrame 查询将默认使用流处理引擎。普通用户将因此迎来内存占用与运行性能的大幅提升,整体来看,流处理引擎的速度预计轻松达到5倍。
为帮助用户顺利过渡到 2.0 版本,我们发布了完整的迁移指南。本文将重点介绍其中几项核心变更。
流处理引擎成为默认选项
这是 2.0 版本影响最大的一项变更。现在对 LazyFrame 调用 collect 方法时,将默认使用流处理引擎,绝大多数查询的内存占用和运行速度都会得到显著优化。之所以需要升级主版本号,是因为流处理引擎默认不保证部分操作(join、group_by、unpivot 等)的行顺序。如果您需要在这些操作中保留可预测的行顺序,可以通过设置 maintain_order=True 开启该功能。
若希望继续将"内存内引擎"设为默认选项,您可以通过设置引擎偏好实现:
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" 现在会默认使用流处理引擎
# 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()
)
# 或在全局范围内将旧的内存内引擎设为默认:
pl.Config.set_engine_affinity("in-memory")
# ...也可以为单个查询指定引擎:
(
lf
.join(other, on="k", how="left")
.collect(engine="in-memory")
)
更严格的 Polars
Polars 秉持"严格校验、快速失败"的原则:错误最好在执行初期就暴露,而非等到运行20分钟后才显现。数据不匹配时的隐式转换应作为可选功能,而非默认行为——因为这类隐式转换可能掩盖潜在bug。在AI驱动开发日益普及的当下,这种严格性的价值愈发凸显。智能代理可以通过调用 collect_schema() 提前验证查询结构,在不生成实际数据的情况下解析类型、发现 schema 层面的不匹配,从而获得快速反馈,加快迭代速度。当然,并非所有错误都能在查询计划编译阶段被发现,部分错误与实际数据相关。对于这类情况,Polars 默认采用更严格的行为,确保不一致性被及时发现,而非默默生成错误结果。
以下是 Polars 变得更严格的几个典型场景:
is_in 操作的无损类型转换
在旧版本中,若对不同数据类型执行 is_in 表达式,Polars 会将两种类型转换为它们的共同超类型,即便这种转换是有损的。下面这个用户ID匹配的例子,就可能因隐式类型不匹配而出错:
# 检查用户ID是否在"标记"账户ID列表中
# (标记ID从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 超出了 Float64 能精确表示的最大整数(2^53 = 9007199254740992),会被自动向下取整为 9007199254740992.0,从而产生误判。
在 2.0 版本中,上述代码会直接抛出错误:InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data.(is_in 无法在 List(Float64) 数据中检查 Int64 值),用户需显式进行类型转换以处理有损转换场景。
严格的拼接操作
横向拼接现在会检查数据长度,不再自动用 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天的标记数被静默填充为null
└─────┴───────┴─────────┘
在 2.0 版本中,上述代码会抛出错误:
ShapeError: cannot concat dataframes with different heights in 'strict' mode(在"严格"模式下,无法拼接高度不同的DataFrame)。如果您确实需要填充缺失值,需显式指定 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.(不支持从u32转换为枚举类型)
应改用:.cat.to(dtype) 实现整数到类别型的转换,用 .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()。这些方法允许您指定解析格式,让您对数据解析过程拥有更多控制权。
以上只是几个典型例子,我们还做了许多其他严格性优化,完整内容请查看迁移指南。
提供更具指导性的错误信息
我们投入大量精力,确保用户或智能代理在使用已废弃的参数时,能够清楚了解后续操作方向。为此新增了两种类型的异常:polars.exceptions.AttributeRemovedError 和 polars.exceptions.ArgumentRemovedError,分别用于处理已移除的属性/方法,以及已移除的参数。
错误信息会直接指向新的API,以下是两个示例:
>>> lf.melt(id_vars="a", value_vars="b")
polars.exceptions.AttributeRemovedError: `melt` was removed in version 2.0;
use `LazyFrame.unpivot` instead, with `index` instead of `id_vars`
and `on` instead of `value_vars`
(`melt` 在2.0版本中已移除,请改用 `LazyFrame.unpivot`,用 `index` 替代 `id_vars`,用 `on` 替代 `value_vars`)
>>> df.join(df, on="a", join_nulls=True)
polars.exceptions.ArgumentRemovedError: the argument 'join_nulls' for
'DataFrame.join' was deprecated in version 1.24 and has been removed
in 2.0.0. It was renamed to 'nulls_equal' in version 2.0.
(`DataFrame.join` 的参数 `join_nulls` 在1.24版本中被废弃,并在2.0.0版本中移除,2.0版本中已更名为 `nulls_equal`)
大多数被移除的功能早已被标记为废弃,如果您一直跟进版本更新,此次变更应该不会影响您的现有流程。如果您认为某些依赖的功能应当被保留,欢迎与我们联系。
最后寄语
Polars 2.0 的核心是更优的默认配置(尤其是流处理引擎)与更完善的API。我们希望这次版本更新能平稳过渡。我们不会将新功能刻意留到大版本发布,而是会在功能就绪后立即推送。
请不要误解,Polars 2.x 版本将比 1.x 版本强大得多。还有许多正在开发中的功能尚未对外详细公布:流处理引擎的完善核外支持、全新的IO插件设计、我们认为将是业界最快的S3读取器、SQL覆盖范围的大幅提升、基于成本的查询优化器、连接重排,以及移除内存映射(mmap)以实现全异步流程。
您可以通过 pip install polars==2.0rc1 安装候选版本进行试用。如有问题,欢迎通过以下渠道联系我们:GitHub Issues 或 Discord社区。