
polars 表达式本身不可直接修改元数据,但可通过序列化→json 解析→字段重写→反序列化的临时方案实现列名与别名的批量变换,适用于开发调试或元编程场景,但因序列化格式不稳定,生产环境应避免依赖。
polars 表达式本身不可直接修改元数据,但可通过序列化→json 解析→字段重写→反序列化的临时方案实现列名与别名的批量变换,适用于开发调试或元编程场景,但因序列化格式不稳定,生产环境应避免依赖。
在 Polars 中,Expr 对象是不可变的,其底层 AST(抽象语法树)不提供公开的、安全的 API 用于遍历或重写节点(如 pl.col("A") → pl.col("A_suffix"))。虽然 expr.meta.root_names() 可读取依赖列名,但 Polars 当前(v1.0+)未暴露任何官方支持的表达式重写机制——既无类似 map_columns() 的高阶变换接口,也无稳定、文档化的 AST 访问器。
不过,借助 Polars 内置的实验性序列化能力,可绕过限制实现目标效果。核心思路是:将表达式序列化为 JSON 格式 AST → 使用 json.loads(..., object_hook=...) 在解析过程中动态修改 "Column" 和 "Alias" 字段 → 将修改后的结构重新序列化为 JSON 字符串 → 调用 pl.Expr.deserialize() 还原为合法表达式。
以下是一个健壮的后缀注入函数示例:
import json
import polars as pl
def suffix_expr(expr: pl.Expr, suffix: str) -> pl.Expr:
"""为表达式中所有 Column 和 Alias 添加指定后缀(实验性,仅用于非生产场景)"""
def _add_suffix(obj):
if isinstance(obj, dict):
# 修改 Column 引用(如 {"Column": "A"} → {"Column": "A_suffix"})
if "Column" in obj:
obj["Column"] = obj["Column"] + suffix
# 修改 Alias 的别名部分(如 ["Agg", ..., "AdotB"] → [..., "AdotB_suffix"])
if "Alias" in obj and len(obj["Alias"]) >= 2:
# Alias 结构为 [inner_expr, alias_name],最后元素为别名字符串
obj["Alias"][-1] = obj["Alias"][-1] + suffix
return obj
# 序列化为 JSON 字符串
ast_json = expr.meta.serialize(format="json")
# 解析并重写
parsed = json.loads(ast_json, object_hook=_add_suffix)
# 重新序列化并反序列化为 Expr
new_ast_json = json.dumps(parsed)
return pl.Expr.deserialize(new_ast_json.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_expr(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 │ └──────────┴──────────┴──────────────┘
⚠️ 重要注意事项:
-
serialize()/deserialize()是内部实验性接口,官方文档明确警告:“Serialization is not stable across Polars versions”。升级 Polars 后该方案可能失效; - JSON AST 结构无版本兼容保证,
"Alias"、"Column"等键名或嵌套层级未来可能调整; - 此方法无法处理复杂嵌套(如子查询、窗口函数中的列引用),也不支持条件分支逻辑;
- 生产代码中应优先采用显式重构:例如将原始表达式封装为函数,接受列名参数,而非依赖 AST 操作:
# ✅ 推荐:清晰、稳定、可测试
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)
df.with_columns(make_dot_expr("A_suffix", "B_suffix", "AdotB_suffix"))综上,尽管技术上可通过序列化“曲线救国”,但本质上 Polars 的设计哲学强调表达式的不可变性与声明式构建。元数据重写需求往往反映上游数据建模或列命名策略待优化——与其修补表达式,不如统一管理列名映射关系,或借助 pl.all().name.suffix("_suffix") 等内置批量操作预处理 DataFrame 结构。

















