
本文介绍如何在 polars 中为表达式(expr)中引用的所有原始列名(root_names)及别名自动添加后缀,通过序列化—修改—反序列化这一非常规但可行的技术路径实现,同时强调其适用边界与稳定性风险。
本文介绍如何在 polars 中为表达式(expr)中引用的所有原始列名(root_names)及别名自动添加后缀,通过序列化—修改—反序列化这一非常规但可行的技术路径实现,同时强调其适用边界与稳定性风险。
在 Polars 的表达式系统中,expr.meta.root_names() 可以安全读取表达式所依赖的原始列名(如 "A" 和 "B"),但 Polars 并未提供官方 API 用于直接重写表达式的内部结构(例如修改 pl.col("A") 为 pl.col("A_suffix"))。这意味着无法通过 .map_root_names() 或类似方法实现列名变换——该功能目前仅存在于 LazyFrame 查询计划层面(如 CUDA 测试中使用的 NodeVisitor),不暴露给用户级表达式操作。
不过,Polars 提供了底层支持:expr.meta.serialize(format="json") 可将表达式序列化为 JSON 格式的抽象语法树(AST),而 pl.Expr.deserialize() 则可将其还原为合法 Expr 对象。利用这一机制,我们能以字符串/JSON 层面的方式精准定位并修改所有 "Column" 键值和 "Alias" 末尾的别名字符串:
import json
import polars as pl
def suffix_all(expr: pl.Expr, suffix: str) -> pl.Expr:
def _add_suffix(obj):
if isinstance(obj, dict):
# 修改 Column 引用
if "Column" in obj and isinstance(obj["Column"], str):
obj["Column"] = obj["Column"] + suffix
# 修改 Alias 的别名部分(Alias 是 [node, name] 结构)
if "Alias" in obj and len(obj["Alias"]) >= 2:
alias_node = obj["Alias"]
if isinstance(alias_node[-1], str):
alias_node[-1] = alias_node[-1] + suffix
return obj
# 序列化 → 解析为 dict → 递归修改 → 重新序列化 → 反序列化
ast_json = expr.meta.serialize(format="json")
modified_ast = json.loads(ast_json, object_hook=_add_suffix)
return pl.Expr.deserialize(json.dumps(modified_ast).encode(), format="json")
使用示例如下:
df = pl.DataFrame({"A_suffix": [2, 7, 3], "B_suffix": [10, 7, 1]})
expr = pl.col("A").dot(pl.col("B")).alias("AdotB")
result = df.with_columns(suffix_all(expr, "_suffix"))
print(result)
输出将正确绑定新列并计算点积:
shape: (3, 3) ┌──────────┬──────────┬──────────────┐ │ A_suffix ┆ B_suffix ┆ AdotB_suffix │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 │ ╞══════════╪══════════╪══════════════╡ │ 2 ┆ 10 ┆ 72 │ │ 7 ┆ 7 ┆ 72 │ │ 3 ┆ 1 ┆ 72 │ └──────────┴──────────┴──────────────┘
⚠️ 重要注意事项:
-
版本不兼容风险:Polars 明确警告
serialize的输出格式“不保证跨版本稳定”。升级 Polars 后,AST 结构可能变更,导致反序列化失败或行为异常; -
非公开契约:该方案依赖内部 AST 实现细节(如
"Alias"为二元列表、"Column"字段存在等),不属于公共 API,未来可能被移除或重构; - 适用场景有限:仅推荐用于开发辅助、元编程原型或临时脚本,切勿用于生产环境的关键数据管道;
-
替代建议:若需长期维护,更健壮的做法是显式重构表达式,例如封装为函数接收列名参数:
def make_dot_expr(col_a: str, col_b: str, alias: str) -> pl.Expr: return pl.col(col_a).dot(pl.col(col_b)).alias(alias) expr = make_dot_expr("A_suffix", "B_suffix", "AdotB_suffix")
综上,虽然 Polars 当前未开放表达式结构重写能力,但通过 serialize/deserialize 可实现灵活的元编程目标。请始终权衡便利性与稳定性,在正式项目中优先采用显式、声明式、版本鲁棒的写法。










