
Polars 的 map_rows 默认将每行作为元组传入,但可通过预提取列名并结合 dict(zip(...)) 构造命名字典,安全地向自定义函数传递键值对形式的行数据,避免“Already mutably borrowed”错误。
polars 的 `map_rows` 默认将每行作为元组传入,但可通过预提取列名并结合 `dict(zip(...))` 构造命名字典,安全地向自定义函数传递键值对形式的行数据,避免“already mutably borrowed”错误。
在 Polars 中,pl.DataFrame.map_rows 是一个逐行映射的高效操作,但它底层以只读、无状态的方式传递行数据——具体表现为 Python 端接收的是一个 tuple,而非带列名的 dict 或 NamedTuple。这导致直接在 lambda 中引用 df.columns(如 lambda x: udf({k:v for k, v in zip(df.columns, x)}))会触发 RuntimeError: Already mutably borrowed:因为 df.columns 是一个动态属性访问,在闭包中被 map_rows 执行时可能与内部借用机制冲突(Rust 层面对 DataFrame 的不可变视图正被持有,而再次访问 df.columns 尝试隐式共享借用,引发冲突)。
✅ 正确做法是提前求值并捕获列名,将其固化为普通 Python 列表,再用于字典构建:
cols = df.columns # ✅ 提前提取,脱离 DataFrame 生命周期绑定 result = df.map_rows(lambda row_tuple: udf(dict(zip(cols, row_tuple))))
该方案简洁、安全且完全保留在 Polars API 内。例如:
import polars as pl
df = pl.DataFrame({
"name": ["Alice", "Bob", "Charlie"],
"age": [25, 30, 35],
"city": ["NYC", "LA", "Chicago"]
})
def process_person(row_dict: dict) -> str:
return f"{row_dict['name']} ({row_dict['age']}) from {row_dict['city']}"
cols = df.columns
out = df.map_rows(lambda r: process_person(dict(zip(cols, r))))
# out 是一列 Series,类型为 pl.String
print(out)
# shape: (3,)
# Series: '' [str]
# [
# "Alice (25) from NYC"
# "Bob (30) from LA"
# "Charlie (35) from Chicago"
# ]
⚠️ 注意事项:
-
map_rows返回的是pl.Series(单列),若udf返回结构化结果(如dict或list),需配合pl.struct()或后续unnest()处理; - 性能上,
map_rows仍优于纯 Python 循环(df.rows(named=True)),但不如原生表达式链;如需保留列名语义或做列级变换,优先考虑pl.all().map_elements(...)+struct表达式组合; -
dict(zip(cols, row_tuple))依赖列顺序与元组顺序严格一致(map_rows保证此一致性),无需额外校验。
总结:这不是 Polars “不支持命名行”,而是设计权衡下的安全约束——通过显式缓存 df.columns,即可零成本获得类 row(i, named=True) 的开发体验,同时坚守 Polars 的高性能与内存安全边界。










