Pre-Release of Polars 2.0

Pre-Release of Polars 2.0

Polars 2.0 预发布

Today we are releasing the first release candidate for Polars 2.0. The definite 2.0 release will land in the following weeks. We don’t aim to make a big feature release of Polars 2.0. In fact we hope it to be a boring experience for you. The reason we bump this major version is that we can get rid of design decisions made in the past that currently block us and then we want to change defaults to more sensible settings that will benefit a greater audience.

今天,我们发布了 Polars 2.0 的首个候选版本(Release Candidate)。正式的 2.0 版本将在未来几周内发布。我们并不打算在 Polars 2.0 中推出大量新功能;事实上,我们希望这次升级对你来说是一个“无感”的体验。我们提升主版本号的原因是,为了摆脱过去那些阻碍我们发展的旧设计决策,并希望将默认设置更改为更合理、能惠及更多用户的配置。

The biggest default change will be that all LazyFrame queries now will run on the streaming engine. Casual Polars users can therefore expect huge improvements in memory usage and performance. In aggregate we expect the streaming engine to be easily 5x faster. To help users transition to 2.0, we have posted a full migration guide. This post will cover a few of the highlights.

最大的默认设置变更在于,所有 LazyFrame 查询现在都将运行在流式处理引擎(streaming engine)上。因此,普通 Polars 用户可以期待内存占用和性能方面的巨大提升。总体而言,我们预计流式引擎的速度可以轻松提升 5 倍。为了帮助用户迁移到 2.0,我们发布了一份完整的迁移指南。本文将介绍其中的一些重点。

Streaming engine as default

流式引擎作为默认设置

This is the biggest impact change of 2.0. Calling collect on a LazyFrame will now default to the streaming engine, leading to massive memory and performance improvements on most queries for users. The reason this required a major version bump is that the streaming engine doesn’t guarantee row-order by default for certain operations (join, group_by, unpivot, etc.). If you require observable row-order in those operations, you can opt in to that by setting maintain_order=True. For users who want to keep using the “in-memory” engine as default, they can do so by setting the engine affinity.

这是 2.0 版本中影响最大的变更。现在,对 LazyFrame 调用 collect 将默认使用流式引擎,这将为大多数查询带来显著的内存和性能优化。之所以需要提升主版本号,是因为流式引擎在默认情况下无法保证某些操作(如 join、group_by、unpivot 等)的行顺序。如果你在这些操作中需要可观察的行顺序,可以通过设置 maintain_order=True 来启用。对于希望继续使用“内存中”(in-memory)引擎作为默认设置的用户,可以通过设置引擎亲和性(engine affinity)来实现。

Stricter Polars

更严格的 Polars

Polars aims to be strict and fail fast. Errors should ideally raise up-front, not 20 minutes into a pipeline. Implicit behavior on data-mismatches should be opt-in, not a default, since those mismatches can hide bugs. This strictness has become even more valuable with the rise of AI-driven development. Agents can validate a query’s structure early by calling collect_schema(), which resolves types and catches schema-level mismatches without materializing any data. This ensures fast feedback for the agents, meaning they can iterate faster.

Polars 的目标是保持严格并快速失败(fail fast)。理想情况下,错误应该在第一时间抛出,而不是在流水线运行 20 分钟后才出现。对于数据不匹配的隐式行为,应该由用户主动选择,而不是作为默认设置,因为这些不匹配可能会掩盖 Bug。随着 AI 驱动开发的兴起,这种严格性变得更加重要。AI 代理可以通过调用 collect_schema() 提前验证查询结构,该方法可以在不实例化任何数据的情况下解析类型并捕获模式层面的不匹配。这确保了代理能获得快速反馈,从而实现更快的迭代。

is_in lossless type-coercion

is_in 的无损类型强制转换

If you run an is_in expression on different data-types, Polars used to cast both types to their common supertype, even if that conversion was lossy. Before 2.0, user_id gets coerced to Float64 to match flagged_ids. But 9007199254740993 sits above 2^53 (9007199254740992), the largest integer float64 can represent exactly, so it silently rounds down to 9007199254740992.0, giving a false positive. In 2.0 this raises: InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data., users should explicitly cast to deal with lossy type conversion.

如果你在不同的数据类型上运行 is_in 表达式,Polars 过去会将两种类型都转换为它们的公共超类型,即使这种转换是有损的。在 2.0 之前,user_id 会被强制转换为 Float64 以匹配 flagged_ids。但 9007199254740993 超过了 2^53(9007199254740992,即 Float64 能精确表示的最大整数),因此它会静默向下舍入为 9007199254740992.0,从而导致误报。在 2.0 中,这会抛出错误:InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data.,用户应显式进行转换以处理有损类型转换。

Strict concatenation

严格的连接(Concatenation)

Horizontal concat will now check lengths instead of silently filling with null. In 2.0 this will raise with: ShapeError: cannot concat dataframes with different heights in 'strict' mode. If padding is what you wanted, you have to explicitly opt-in to that with how="horizontal_extend". Making that intention clear to the reader.

水平连接(Horizontal concat)现在会检查长度,而不是静默地填充 null 值。在 2.0 中,这会抛出错误:ShapeError: cannot concat dataframes with different heights in 'strict' mode。如果你确实需要填充,必须通过 how="horizontal_extend" 显式启用,从而向阅读代码的人明确你的意图。

Removal of casts in favor of dedicated methods/constructors

移除 cast 方法,改用专用方法/构造函数

Another one worth mentioning is the removal of many casts that were ambiguous or should be applied via their dedicated parsing expression, leading to one obvious way to parse data. For example, casting strings to temporal data-types now requires .str.to_date() / .str.to_datetime(). These allow you to apply a parsing format, giving you more control over how the data is parsed.

另一个值得一提的变化是移除了许多模棱两可的 cast 操作,这些操作本应通过专用的解析表达式来完成,这样可以确保解析数据的方式更加明确。例如,将字符串转换为时间数据类型现在需要使用 .str.to_date().str.to_datetime()。这些方法允许你应用解析格式,从而让你能更好地控制数据的解析方式。