您好,我正在研究这个同人小说项目,该项目是 pypolars 到 R 的全功能 + 语法翻译,称为“minipolars”。
我了解 pypolars API,例如一般的 DataFrame 引发不可变行为或与“写时复制”行为相同。大多数改变 DataFrame 对象的方法都会返回一个廉价的副本。 我知道的例外是 DataFrame.extend 和 @columns.setter。 在 R 中,大多数 API 都致力于实现严格的不可变行为。我想既支持严格不可变的行为,又支持可选的类似 pypol 的行为。 Rust-polars API 有许多可变操作+生命周期等等,但可以理解的是,它完全与性能和表达能力有关。
R 库
data.table
API 有时确实会偏离不可变行为。然而,所有此类可变操作都带有前缀 set_
或使用集合运算符 :=
。
通过可变行为,我想到了例如在定义变量
.extend()
后执行方法 df_mutable_copy
并且仍然影响值 df_mutable_copy
。
import polars as pl
df1 = pl.DataFrame({"foo": [1, 2, 3], "bar": [4, 5, 6]})
df2 = pl.DataFrame({"foo": [10, 20, 30], "bar": [40, 50, 60]})
df_copy = df1
df_copy_old_shape = df_copy.shape
df1.extend(df2)
df_copy_new_shape = df_copy.shape
#extend was a operation with mutable behaviour df_copy was affected.
df_copy_old_shape != df_copy_new_shape
大多数Python Polars API实际上是Polars惰性的包装器。
(df.with_columns(..)
.join(..)
.group_by()
.select(..)
翻译为:
(df.lazy().with_columns(..).collect(no_optimization=True)
.lazy().join(..).collect(no_optimization=True)
.lazy().group_by().collect(no_optimization=True)
.lazy().select(..).collect(no_optimization=True)
这意味着几乎所有表达式都在 Polars 查询引擎上运行。查询引擎本身决定是否可以就地完成操作,或者是否应该克隆数据(写入时复制)。
Polars 实际上具有“写入时复制”功能,因为它仅在数据不共享时进行复制。如果我们是唯一的所有者,我们就会就地变异。我们可以这样做是因为 Rust 有一个借用检查器,所以如果我们拥有数据并且引用计数为 1,我们就可以改变数据。
我建议您实现 R-polars API,类似于我们在 python 中所做的事情。然后所有操作都可以变得纯粹(例如返回一个新的
Series/Expr/DataFrame
)并且pollars将决定何时就地变异。
不用担心复制数据。所有数据缓冲区都包含在
Arc
中,因此我们只增加引用计数。