
在 Polars 中,自定义函数需直接返回多个 Expr 对象(而非 struct),再通过生成器表达式或字典解包动态重命名并注入列,才能高效、可扩展地添加多列。
在 polars 中,自定义函数需直接返回多个 `expr` 对象(而非 `struct`),再通过生成器表达式或字典解包动态重命名并注入列,才能高效、可扩展地添加多列。
Polars 的 with_columns() 方法原生支持批量添加列,但不支持直接将 pl.struct() 的结果 .alias(["col1", "col2"]) 拆分为多列——这是初学者常见误区。pl.struct() 仅创建嵌套结构列(即单列含 struct 类型),无法自动展开为平行列。正确做法是让自定义函数直接返回多个独立的 Expr 实例,再通过 Polars 表达式组合机制完成列名绑定与插入。
✅ 推荐实现:函数返回元组,配合 enumerate 动态别名
import polars as pl
import numpy as np
def _func(x: pl.Expr) -> tuple[pl.Expr, pl.Expr]:
x1 = x + 1
x2 = x + 2
return x1, x2 # 直接返回两个 Expr,非 struct!
df = pl.DataFrame({"test": np.arange(1, 11)})
# 方式1:按序号自动命名(推荐,可扩展至 N 列)
result = df.with_columns(
expr.alias(f"test{i+1}")
for i, expr in enumerate(_func(pl.col("test")))
)
print(result)输出:
shape: (10, 3) ┌──────┬───────┬───────┐ │ test ┆ test1 ┆ test2 │ │ --- ┆ --- ┆ --- │ │ i32 ┆ i32 ┆ i32 │ ╞══════╪═══════╪═══════╡ │ 1 ┆ 2 ┆ 3 │ │ 2 ┆ 3 ┆ 4 │ │ … ┆ … ┆ … │ └──────┴───────┴───────┘
✅ 优势:无需硬编码列名,轻松适配任意数量返回值(如后续改为 x+1, x+2, x+3, x+4,仅需保持 enumerate 逻辑即可)。
? 灵活命名方案:使用 zip 或字典解包
若需自定义列名(如 "a"、"b"),有两种简洁写法:
方式2:zip + 生成器(语义清晰)
df.with_columns(
expr.alias(name)
for expr, name in zip(_func(pl.col("test")), ["a", "b"])
)方式3:字典解包(函数式风格,适合配置驱动)
df.with_columns(
**dict(zip(["a", "b"], _func(pl.col("test"))))
)两种方式均避免了手动 .alias() 链式调用,且天然支持任意长度名称列表与表达式序列对齐(长度不匹配时会抛出 ValueError,利于早期发现问题)。
⚠️ 注意事项与最佳实践
- ❌ 不要使用 pl.struct([...]).alias([...]):Polars 不支持结构体列的“多别名展开”,该写法静默失败或报 SchemaError。
- ✅ 函数签名建议明确标注返回类型(如 tuple[pl.Expr, ...]),提升可维护性与 IDE 支持。
- ? 若计算逻辑复杂,可封装为 pl.UDF(用户定义函数),但纯表达式链(如本例)性能更优,应优先使用。
- ? 所有返回的 Expr 必须基于同一输入列(如 pl.col("test")),否则可能触发 ComputeError;跨列运算需在 with_columns() 外统一组织。
通过以上方法,你不仅能精准实现双列输出,还能无缝扩展至任意多列场景,兼顾代码简洁性、可读性与 Polars 的高性能计算范式。

















