- 数据分析
- 大数据
【免费下载链接】polars
Extremely fast Query Engine for DataFrames, written in Rust
导读
在 Polars 中,DataTypeExpr.list是数据类型表达式(DataType Expression)体系中专用于 List(可变长度列表)类型的访问器命名空间,而inner_dtype()是其中(也是整个list命名空间下)公开的方法,用于在表达式上下文中惰性地获取一个 List 类型内部承载的元素类型。本文以 dt_list.rst 为骨架,结合 Python 端实现、Rust 绑定层、DSL 求值逻辑 与 测试用例,讲清DataTypeExpr.list.inner_dtype()的用法、实现链路、错误语义与调试方法。读完后你将掌握如何在 LazyFrame 的表达式构建期按运行时 schema 动态取得 List 元素类型,并理解其与顶层inner_dtype()、.arr、.struct等访问器的区别。
一、背景:什么是 DataTypeExpr,什么是 List 类型
1.1 List 类型:Polars 的嵌套(Nested)数据类型
Polars 的pl.List是一种可变长度的嵌套类型,它由两部分构成:一个表示"容器"的List类型,以及一个表示"内部元素"的inner数据类型。在 py-polars/src/polars/datatypes/classes.py 中可以看到其定义:
class List(NestedType): """Variable length list type.""" inner: PolarsDataType def __init__(self, inner: PolarsDataType | PythonDataType) -> None: self.inner = polars.datatypes.parse_into_dtype(inner)即pl.List(pl.Int64)表示"列表中的每个元素都是 Int64",其字符串表示为list[i64]。List 可以无限嵌套(如list[list[i64]]),元素也可以是 Struct、Array 等任意类型。
1.2 DataTypeExpr:惰性化的 DataType
DataTypeExpr的核心定位在 datatype_expr.py 的 docstring 中描述得很清楚:
A lazily instantiated
DataTypethat can be used in anExpr.
它把"一个数据类型"变成"一个可参与表达式运算的惰性句柄",从而可以在查询计划尚未完全确定 schema 时引用数据类型。注意:该功能的 docstring 明确标注了.. warning:: This functionality is considered **unstable**.,即 API 可能在不通知的情况下变更,属于实验性功能。
获取一个DataTypeExpr有三种典型途径(从源码结构看,classes.py 的to_dtype_expr将静态类型包装为DataTypeExpr::Literal):
pl.Int64.to_dtype_expr()/pl.List(pl.Int8).to_dtype_expr():将静态类型包装成数据类型表达式;pl.dtype_of("col_name"):引用某列在运行时(按给定 schema)解析出的类型;pl.self_dtype():引用"当前表达式自身的输出类型"。
1.3 访问器体系:.list只是其中之一
DataTypeExpr上公开了四类命名空间访问器(见 datatype_expr.py):.list(List 相关)、.arr(Array 相关)、.struct(Struct 相关)以及.int(整数转换,位于 Rust 端)。本文主角DataTypeExpr.list正是 dt_list.rst 所声明的对象——该 API 参考页指出,DataTypeExpr.list属性下可用的方法即inner_dtype。
二、DataTypeExpr.list.inner_dtype() 的 Python 接口
2.1 命名空间类的定义
DataTypeExpr.list属性返回DataTypeExprListNameSpace实例,其完整实现只有寥寥数行(py-polars/src/polars/datatype_expr/list.py):
class DataTypeExprListNameSpace: """Namespace for list datatype expressions.""" _accessor = "list" def __init__(self, expr: pl.DataTypeExpr) -> None: self._pydatatype_expr = expr._pydatatype_expr def inner_dtype(self) -> pl.DataTypeExpr: """Get the inner DataType of list.""" return pl.DataTypeExpr._from_pydatatype_expr( self._pydatatype_expr.list_inner_dtype() )要点:
- 命名空间对象只是持有底层
PyDataTypeExpr的透明包装,构造时把外层表达式的_pydatatype_expr透传进去; inner_dtype()无参数、返回pl.DataTypeExpr——返回的仍然是一个惰性表达式,只有当它被放入select/with_columns等表达式上下文或调用collect_dtype时才会真正解析出具体类型;- 这是
.list命名空间下唯一的公开方法(_accessor = "list"同时用于 IDE 自动补全与文档生成)。
2.2 直接用法示例
下面两个示例均可直接在 Polars 环境中运行:
import polars as pl # 1) 静态 List 类型:取出元素类型并与之比较 list_dtype = pl.List(pl.String).to_dtype_expr() result = pl.select(list_dtype.list.inner_dtype() == pl.String).to_series().item() assert result is True # 2) 在惰性查询中为表达式指定返回值类型 lf = pl.LazyFrame({"a": [["x", "y"], ["z"]]}) # dtype_of("a") 在运行时解析为 list[str],再取其 inner_dtype 即 str out = lf.with_columns( pl.col.a.map_batches(lambda s: s, return_dtype=pl.dtype_of("a").list.inner_dtype()) ).collect() print(out.schema)示例 2 正是 test_lit.py 中pl.lit(None, pl.dtype_of(pl.lit(["abc"])).list.inner_dtype())所演示的典型场景:在构造字面量 / map 回调时,借用"列的类型推导"来声明返回值类型,从而避免硬编码 schema。
三、底层实现链路:从 Python 到 Rust
inner_dtype的求值跨越了三个层次,逐层印证如下:
3.1 Python -> Rust 绑定层
Python 端的self._pydatatype_expr.list_inner_dtype()对应 crates/polars-python/src/expr/datatype.rs 中PyDataTypeExpr的方法:
pub fn list_inner_dtype(&self) -> Self { self.inner.clone().list().inner_dtype().into() }它把调用委托给 Rust 核心的DataTypeExpr。
3.2 DSL 层:InnerDataType 表达式变体
在 crates/polars-plan/src/dsl/datatype_expr.rs 中,DataTypeExpr::list()返回一个命名空间包装,其inner_dtype(同文件 L385-L392)构造出带校验标记的InnerDataType变体:
impl DataTypeExprListNameSpace { pub fn inner_dtype(self) -> DataTypeExpr { DataTypeExpr::InnerDataType { input: Box::new(self.0), validation: Some(SequenceKind::List), } } }与之对照,顶层DataTypeExpr::inner_dtype()(同文件 L254-L259)构造的InnerDataType的validation为None,而.arr.inner_dtype()则携带SequenceKind::Array。这正是.list.inner_dtype()与顶层inner_dtype()的本质区别:前者在求值时强制校验输入必须是 List 类型。
3.3 求值逻辑:类型解析与校验
InnerDataType变体的真正求值发生在 datatype_expr.rs 的 into_datatype_impl:
D::InnerDataType { input, validation } => { let dt = into_datatype_impl(*input, schema, self_dtype)?; let Some(validation) = validation else { return dt.try_into_inner_dtype(); }; match (dt, validation) { (DataType::List(inner), SequenceKind::List) => *inner, (dt, SequenceKind::List) => { polars_bail!(SchemaMismatch: "expected `list` type but got `{dt}`") }, ... } }语义非常明确:
- 先递归解析输入表达式得到实际类型
dt(例如list[str]); - 若未带校验(顶层
inner_dtype()),调用 polars-core 的try_into_inner_dtype直接解包List(inner)/Array(inner); - 若带
SequenceKind::List校验(即本文的.list.inner_dtype()),则只有当dt是DataType::List时才返回其 inner,否则抛出SchemaMismatch错误,错误信息为expected 'list' type but got '{dt}'。
由此可推断设计意图:.list命名空间提供的是"类型安全的访问器",让用户明确表达"我期望这是一个 List 类型",一旦 schema 与预期不符就在求值期快速失败,而不是静默返回错误结果。这与.arr、.struct的访问器设计一脉相承。
四、错误语义与边界行为
4.1 对非 List 类型调用会怎样
在 test_datatype_exprs.py 的 test_inner_dtype 中,仓库用测试固化了这套错误语义:
arr_dtype = pl.Array(pl.String, 2).to_dtype_expr() # 对 Array 类型调用 .list.inner_dtype() -> SchemaError with pytest.raises(pl.exceptions.SchemaError): arr_dtype.list.inner_dtype().collect_dtype({}) # 对 Struct 类型调用顶层 inner_dtype() -> InvalidOperationError with pytest.raises(pl.exceptions.InvalidOperationError): pl.Struct({"x": pl.Int8}).to_dtype_expr().inner_dtype().collect_dtype({}) # 对 List 类型调用 .arr.inner_dtype() -> SchemaError list_dtype = pl.List(pl.String).to_dtype_expr() with pytest.raises(pl.exceptions.SchemaError): list_dtype.arr.inner_dtype().collect_dtype({})可归纳为三类错误:
- SchemaMismatch(
SchemaError):类型"存在但不匹配",如对array调用.list访问器——错误信息形如expected 'list' type but got 'array'; - InvalidOperation(
InvalidOperationError):类型根本不具备 inner 概念,如对struct调用inner_dtype()(其 "inner" 需要按字段名/索引获取,见.struct命名空间); - 对
list[list[i64]]这种嵌套 List 调用,仅剥离最外层,返回list[i64](这正是inner_dtype与leaf_dtype的差异,后者会递归到最内层叶子类型,见 dtype.rs)。
4.2 返回值仍是 DataTypeExpr,不是 DataType
inner_dtype()返回DataTypeExpr,这意味着你需要一个"解析上下文"才能拿到具体的pl.DataType。最直接的调试手段是 collect_dtype:
pl.List(pl.Int64).to_dtype_expr().list.inner_dtype().collect_dtype({}) # Int64collect_dtype接受四种上下文:pl.Schema、SchemaDict(即{"col": dtype}字典)、pl.DataFrame或pl.LazyFrame,它会将 DataTypeExpr 放入该上下文解析出最终类型,官方文档将其定位为"调试数据类型表达式的有用函数"。
五、与 DataTypeExpr 其他方法的组合实战
inner_dtype通常不是孤立使用的,而是与其他表达式方法组合,构成"类型感知"的查询逻辑。以下组合均在仓库文档与测试中有据可查:
5.1 与==比较:按元素类型分流
DataTypeExpr重载了__eq__(datatype_expr.py L68-L85),与pl.DataType、DataTypeClass或另一个DataTypeExpr比较时返回一个布尔Expr(而非 Python bool):
pl.select(pl.List(pl.Int64).to_dtype_expr().list.inner_dtype() == pl.Int64) # shape: (1, 1) literal: bool, true这可用于构建按 schema 分派的pl.when(...)条件,例如"如果某 List 列的元素是数值类型,则求和,否则取长度"。
5.2 与pl.dtype_of组合:运行时 schema 驱动
结合pl.dtype_of("col")可以写出不依赖硬编码类型的通用逻辑:
inner = pl.dtype_of("scores").list.inner_dtype() lf.with_columns(pl.col.scores.cast(pl.List(inner)))5.3 与wrap_in_list/wrap_in_array反向组合
DataTypeExpr还提供wrap_in_list()与wrap_in_array(width=...)(datatype_expr.py L154-L177),可以视为inner_dtype的逆操作:inner_dtype剥离一层容器,wrap_in_list则包回一层。二者配合可实现对嵌套深度的动态调整。
5.4 与 selectors 组合:类型分类
matches(selector)允许用 polars 选择器(如cs.list()、cs.numeric())做类型分类判断。在 test_datatype_exprs.py 的test_classification中,仓库用一组覆盖 30 余种类型的参数化测试验证了dtype_expr.matches(selector)的分类正确性,其中pl.List(pl.Null)应命中cs.list()等嵌套类型选择器。这为"先判断外层是 List,再用inner_dtype判断内层"的两段式类型逻辑提供了完整支撑。
六、测试与验证:仓库如何保障该 API 的正确性
围绕DataTypeExpr.list.inner_dtype,仓库提供了多层测试保障:
- test_datatype_exprs.py 中的
test_inner_dtype:覆盖 List / Array 各自访问器的正反用例,以及SchemaError/InvalidOperationError的抛出路径; - test_lit.py:在
pl.lit场景下验证pl.dtype_of(pl.lit(["abc"])).list.inner_dtype()可用于声明字面量的类型,确保"动态取类型 → 作为 return_dtype / 字面量 dtype"的闭环可用; - Rust 端 datatype.rs 的 Debug 实现 将带
SequenceKind::List校验的表达式格式化为.list.inner(),说明 DSL 层将.list.inner_dtype()表达为一种可解释、可打印的语法结构。
七、小结与使用建议
| 调用形式 | 校验 | 输入类型 | 结果 |
|---|---|---|---|
dtype_expr.inner_dtype() | 无 | List/Array(Array需启用 dtype-array 特性) | 剥离一层内层类型;非嵌套类型抛InvalidOperationError |
dtype_expr.list.inner_dtype() | SequenceKind::List | 必须是List | 剥离 List 的 inner;Array等其他类型抛SchemaError |
dtype_expr.arr.inner_dtype() | SequenceKind::Array | 必须是Array | 剥离 Array 的 inner |
使用建议:
- 当你的逻辑明确期待 List 列时,优先使用
.list.inner_dtype()以获得早期类型校验; - 当类型可能是 List 也可能是 Array 时,用无校验的顶层
inner_dtype(),并自行处理错误; - 用
collect_dtype(context)在开发期验证表达式解析出的实际类型; - 留意该功能的unstable 标注(见 datatype_expr.py 的 warning),在版本升级时关注 API 变更。
DataTypeExpr.list命名空间虽小,却是 Polars 类型系统与表达式系统交汇处的关键接口——它以极小的 API 面,把"运行时 schema 中的嵌套类型信息"带入了惰性查询的构建期,为编写 schema 无关的通用数据管道提供了基础能力。
- 数据分析
- 大数据
【免费下载链接】polars
Extremely fast Query Engine for DataFrames, written in Rust
相关推荐
QQ空间历史说说找回完整指南:新手用 GetQzonehistory 导出Excel全流程
QQ空间历史说说找回完整指南:新手用 GetQzonehistory 导出Excel全流程 翻QQ空间首页,翻到几年前就翻不下去了?那些旧说说多半没被删,只是不
网页爬虫数据分析Dagger TypeScript SDK ListTypeDef 类详解:模块列表类型定义与元素类型获取
Dagger TypeScript SDK ListTypeDef 类详解:模块列表类型定义与元素类型获取 ListTypeDef 是 Dagger 模块类型系
DevOpsCI/CD后端CLI云原生Open Mercato Feature Toggles 实战:运行时开关与租户级覆盖的完整指南
Open Mercato Feature Toggles 实战:运行时开关与租户级覆盖的完整指南 Open Mercato 的 Feature Toggles(
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考