Daft 类型转换完全指南:DataType 与 Python 类型之间的双向映射机制
【免费下载链接】DaftHigh-performance data engine for AI and multimodal workloads. Process images, audio, video, and structured data at any scale项目地址: https://gitcode.com/GitHub_Trending/da/Daft
本文围绕 Daft 官方文档 Type Conversions 展开,系统讲解 Daft DataType 与 Python 类型之间的双向转换规则:Daft 到 Python 的取值映射(如Series.to_pylist)、Python 类型到 Daft 类型的静态推断(如@daft.func的返回类型提示)、以及基于 Python 对象字面值的运行时推断(如daft.from_pydict)。读完本文,你将能够准确预测任意 Python 类型/对象在 Daft 中被映射为哪种 DataType,也能结合 daft/datatype.py 的源码实现理解各推断分支的边界条件与警告行为。
Daft 到 Python:取值时的类型映射
当你从 Daft 数据中取出 Python 值时(典型场景包括 [Series.to_pylist][daft.series.Series.to_pylist]、将列 [cast][daft.expressions.Expression.cast] 到 Python 类型、以及传递给@daft.func装饰函数的参数),Daft DataType 按如下规则映射为 Python 类型:
| Daft DataType | Python Type |
|---|---|
| Null | None |
| Boolean | bool |
| Utf8 | str |
| Binary FixedSizeBinary | bytes |
| Int8 Uint8 Int16 UInt16 Int32 UInt32 Int64 UInt64 | int |
| Timestamp | datetime.datetime |
| Date | datetime.date |
| Time | datetime.time |
| Duration | datetime.timedelta |
| Interval | not supported |
| Float32 Float64 | float |
| Decimal128 | decimal.Decimal |
| List[T] FixedSizeList[T, n] | list[T] |
| Struct[k1: T1, k2: T2, ...] | { "k1": <T1>, "k2": <T2>, ... } |
| Map[K, V] | list[tuple[K, V]](默认);maps_as_pydicts下为dict[K, V] |
| Tensor[T] FixedShapeTensor[T, [...]] | numpy.typing.NDArray[T] |
| SparseTensor[T] FixedShapeSparseTensor[T, [...]] | {"values": <T>,"indices": [<int>],"shape": [<int>]} |
| Embedding[T] | numpy.typing.NDArray[T] |
| Image | numpy.typing.NDArray[numpy.uint8 \| numpy.uint16 \| numpy.float32] |
| Python | Any |
| Extension[T] | T |
从源码结构看,上述行为对应 Rust 侧impl IntoPyObject for Literal的实现,入口位于 src/daft-core/src/lit/python.rs(文档中的注释即要求该表与 Rust 的Literal转 Python 对象行为保持一致)。
Map 类型的两种取值模式:maps_as_pydicts
对Map[K, V]列取 Python 值时,Daft 默认转换为list[tuple[K, V]](关联列表),目的是保留重复键与键的顺序。若希望得到 Pythondict,可在Series.to_pylist等接口上传入maps_as_pydicts参数:
maps_as_pydicts="lossy":遇到重复键时保留最后一个值,并发出警告;maps_as_pydicts="strict":遇到重复键时直接抛出异常。
该参数在 Python 层的签名与文档位于 daft/series.py#L231-L241,底层由self._series.to_pylist(maps_as_pydicts)转发给 Rust 实现完成实际转换。选择建议:如果下游逻辑依赖"每个键唯一"的 dict 语义且能容忍静默丢失,用"lossy";如果数据源保证键唯一、或需要尽早暴露脏数据,用"strict"更稳妥。
Python 类型到 Daft:静态类型推断
当 Daft 需要根据Python 类型(而非具体值)推导 DataType 时——例如从@daft.func装饰函数的类型注解推断返回列的类型——使用的是DataType.infer_from_type类方法。你可以直接调用它来验证任意 Python 类型的推断结果:
| Python Type | Daft DataType |
|---|---|
NoneType | Null |
bool | Boolean |
str | Utf8 |
bytes | Binary |
int | Int64 |
float | Float64 |
datetime.datetime | Timestamp[us] |
datetime.date | Date |
datetime.time | Time[us] |
datetime.timedelta | Duration[us] |
list[T] | List[T] |
dict[K, V] | Map[K, V] |
typing.TypedDict("...", { "k1": T1, "k2": T2, ... }) | Struct[k1: T1, k2: T2, ...] |
tuple[T0, T1, ..., TN](实际类型中不含省略号) | Struct[_0: T0, _1: T1, ..., _N: TN] |
tuple[T, ...] | List[T] |
带序列化字段f1: T1,f2: T2, ... 的pydantic.BaseModel | Struct[f1: T1, f2: T2, ...] |
numpy.ndarraytorch.Tensortensorflow.Tensorjax.Arraycupy.ndarray | Tensor[Python] |
numpy.typing.NDArray[T] | Tensor[T] |
torch.FloatTensor | Tensor[Float32] |
torch.DoubleTensor | Tensor[Float64] |
torch.ByteTensor | Tensor[UInt8] |
torch.CharTensor | Tensor[Int8] |
torch.ShortTensor | Tensor[Int16] |
torch.IntTensor | Tensor[Int32] |
torch.LongTensor | Tensor[Int64] |
torch.BoolTensor | Tensor[Boolean] |
jaxtyping类型(见下文 jaxtyping 小节) | Tensor 或 FixedShapeTensor |
numpy.bool_ | Boolean |
numpy.int8 | Int8 |
numpy.uint8 | UInt8 |
numpy.int16 | Int16 |
numpy.uint32/numpy.uint64等对应 numpy 整型 | UInt32 / UInt64 等 |
numpy.float32 | Float32 |
numpy.float64 | Float64 |
numpy.datetime64 | Timestamp[us] |
pandas.Series | List[Python] |
PIL.Image.Image | Image[MIXED] |
daft.Series | List[Python] |
| 其他所有类型 | Python |
推断实现的关键分支(源码解读)
infer_from_type的完整实现位于 daft/datatype.py#L183-L404,其中几个容易被忽略的分支值得注意:
- Union 类型:先对 Union 的每个成员递归推断。若所有成员推断出同一种 DataType,则取之;若是
Optional[X](即Union[NoneType, X]),则退化为X;否则兜底为Python类型。 - TypedDict 与 dict 的区别:
dict[K, V]推断为Map[K, V],因为仅从类型上无法得到具体的字段名;而TypedDict的键是已知的,因此能推断为Struct。源码中若发现 TypedDict 的键不是字符串,会降级为Map[Python, Python]并告警。 - tuple 的两种形态:
tuple[int, str]这类定长元组映射为Struct[_0: ..., _1: ...](字段名为自动生成的下标);而tuple[T, ...](带省略号的无限元组)则映射为List[T]。 - Pydantic V2 支持:对
pydantic.BaseModel的推断仅支持 Pydantic V2(源码中显式校验2.0.0 <= version < 3.0.0),并按model_config的serialize_by_alias决定 Struct 字段名取别名还是属性名,同时会把model_computed_fields(computed field)也纳入 Struct 字段。 - Tensor 类型族的精确匹配:
torch的 7 种具名 Tensor 子类各自映射到对应 dtype;而泛化的torch.Tensor、tensorflow.Tensor、jax.Array、cupy.ndarray由于无法在类型层面得到 dtype,统一推断为Tensor[Python]。numpy 侧,NDArray[T]的第二个类型参数会被解出并映射为内层 dtype,解不出来时告警并回退Tensor[Python]。 - 无法静态推断的兜底:
decimal.Decimal类型(无法从类型得到 precision/scale)、裸的pandas.Series、daft.Series都会发出警告并回退为Python或List[Python];表外的任意类型一律落到Python类型分支。
jaxtyping:从注解中推断 dtype 与 shape
jaxtyping 库为 NumPy、PyTorch、TensorFlow、JAX 的数组类型提供 dtype 与 shape 注解。Daft 可以原生解析jaxtyping注解,同时推断出张量的内层 dtype 和形状。对应实现是 daft/datatype.py 中的_infer_from_jaxtyping。
示例:
jaxtyping.Float64[jaxtyping.Array, "1 2 3 4"]→FixedShapeTensor[Float64, [1, 2, 3, 4]]jaxtyping.Int8[torch.Tensor, "dim1 dim2"]→Tensor[Int8]
Dtype 推断
| jaxtyping Type | Daft DataType |
|---|---|
Bool | Boolean |
Int8 | Int8 |
UInt8 | UInt8 |
Int16 | Int16 |
UInt16 | UInt16 |
Int32 | Int32 |
UInt32 | UInt32 |
Int64IntInteger | Int64 |
UInt64UInt | UInt64 |
Float32 | Float32 |
Float64FloatReal | Float64 |
| Everything else | Python |
注:这里得到的 DataType 是结果Tensor/FixedShapeTensor的内层类型。
Shape 推断
jaxtyping类型的第二个泛型参数是一个空格分隔的符号字符串,表示数组形状。Daft 的推断策略(与源码中"仅当所有维度都是固定值_FixedDim时才写入 shape"的逻辑一致):
- 若所有维度均为固定尺寸,推断为
FixedShapeTensor,例如"1 2 3"、"rows=4 cols=3"、""(标量形状); - 否则推断为
Tensor,例如"dim1 dim2"、"512 512 _"、"... 1 2 3"。
Python 对象到 Daft:运行时值推断
除了上表的类型级映射,Daft 在未显式指定类型、直接把 Python 对象转换为 Daft 列时(典型场景:daft.from_pydict、Series.from_pylist),还能从对象的具体值中提取类型信息,得到比纯类型推断更精确的 DataType。对应方法为DataType.infer_from_object——其实现是通过Series.from_pylist([obj])构造单元素 Series 再读取其datatype(),因此你可以用同一入口验证任意对象的推断结果。
在类型级映射的基础上,对象级推断有以下额外行为:
| Python Object | Daft Type |
|---|---|
大于 2^63-1(Int64 最大值)的int值 | UInt64 |
形如{ "k1": <T1>, "k2": <T2>, ... }的dict | Struct[k1: T1, k2: T2, ...] |
小数点后 N 位的decimal.Decimal | Decimal128[precision=38, scale=N] |
元素类型为T的pandas.Series | List[T] |
元素类型为T的daft.Series | List[T] |
带有 numpy dtypeT的numpy.ndarray/torch.Tensor/tensorflow.Tensor/jax.Array/cupy.ndarray | Tensor[T] |
单位为U的numpy.datetime64 | U= "Y"、"M"、"W"、"D" 时为 Date;U= "h"、"m"、"s" 时为 Timestamp[s];U= "ms" 时为 Timestamp[ms];U= "us" 时为 Timestamp[us];U= "ns"、"ps"、"fs"、"as" 时为 Timestamp[ns] |
模式为M的PIL.Image.Image(支持的模式:L、LA、RGB、RGBA) | Image[M] |
可以看到,对象级推断的核心价值在于利用运行时信息收紧类型:dict的键值对能落成具体字段的 Struct 而非 Map,Decimal能定出 scale,datetime64能按单位精细区分 Date/Timestamp 及各精度,PIL 图像能保留具体通道模式。这与"Python 到 Daft"一节中dict只能推断为Map[K, V]、PIL.Image.Image只能推断为Image[MIXED]形成了清晰对照——类型层面看不到键名和值范围,对象层面可以。
实践建议
- 写 UDF 时优先声明类型注解:
@daft.func的返回类型注解会走infer_from_type路径,直接决定输出列的 DataType。给张量返回值使用jaxtyping或具名 torch Tensor 类型,可以获得带 dtype(甚至带 shape)的 Tensor 列,而不是宽泛的Tensor[Python]。 - 需要精确类型时用
cast显式声明:对象级推断无法覆盖的场景(如混合类型列表),应通过 [Expression.cast][daft.expressions.Expression.cast] 显式指定目标类型,而不是依赖推断。 - 取 Map 列值前先确认键唯一性:默认
list[tuple[K, V]]是最安全的形式;只有确认数据干净或可接受覆盖语义时,再使用maps_as_pydicts="lossy"/"strict"换取 dict 便利性。 - 验证推断结果:怀疑某个注解/对象推断结果时,直接调用
DataType.infer_from_type/DataType.infer_from_object打印结果即可,二者行为与转换管线保持一致。
参考
- 类型转换规则原文档:docs/api/datatypes/type_conversions.md
- Python 类型推断实现:daft/datatype.py(
infer_from_type、_infer_from_jaxtyping、infer_from_object) to_pylist与maps_as_pydicts:daft/series.py- Rust 侧字面量转 Python 的实现:src/daft-core/src/lit/python.rs
【免费下载链接】DaftHigh-performance data engine for AI and multimodal workloads. Process images, audio, video, and structured data at any scale项目地址: https://gitcode.com/GitHub_Trending/da/Daft
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考