关于Polars自动将Pydantic对象转换为字典(结构体)的疑问及阻止方法咨询
你观察得完全正确——Polars确实会自动把Pydantic模型实例转换成字典(对应Polars的Struct类型),本质上它会调用Pydantic模型的model_dump()方法来完成这个转换。这是因为Polars的Struct类型和Pydantic模型的键值对结构天然兼容,Polars会优先把这种结构化对象映射成自己的原生Struct类型,而不是保留原始的Python对象。
接下来聊聊你遇到的报错问题:当你指定return_dtype=pl.Object时,Polars抛出了类型不匹配的错误,这是因为在agg上下文的map_batches操作中,Polars会自动推断你的wrap函数返回的输出结构和Struct类型完全匹配(毕竟Cat模型的字段刚好对应Struct的字段),而你强制指定了pl.Object,两者冲突就导致了报错。
要阻止这种自动转换、保留原始的Pydantic对象,你可以试试下面这些方法:
方法1:初始DataFrame就指定pl.Object dtype
在创建包含Pydantic对象的列时,明确告诉Polars用pl.Object类型存储,这样它就不会一开始就把Cat转成Struct:
class Cat(pydantic.BaseModel): name: str age: int cats = [Cat(name="a", age=1), Cat(name="b", age=2)] # 用pl.Series指定dtype为pl.Object df = pl.DataFrame({"cats": pl.Series(cats, dtype=pl.Object)}) df = df.with_columns(pl.lit(0).alias("acq_num"))
方法2:让map_batches返回pl.Object类型的Series
修改你的wrap函数,让它返回一个标记为pl.Object dtype的Series,同时在map_batches里明确指定return_dtype=pl.Object:
def wrap(batch: pl.Series) -> pl.Series: # 针对批量数据生成包含Cat对象的Series,指定dtype为pl.Object return pl.Series([Cat(name="c", age=3) for _ in range(len(batch))], dtype=pl.Object) df = df.group_by("acq_num").agg( pl.col("cats").map_batches(wrap, return_dtype=pl.Object).alias("cats") ) # 现在检查类型,应该是Cat类实例 type(df["cats"][0][0]) # <class '__main__.Cat'>
方法3:改用map_elements处理单个元素
如果你的逻辑是对每个元素单独处理,map_elements比map_batches更直观,也更容易保留原始对象:
df = df.group_by("acq_num").agg( pl.col("cats").map_elements( lambda x: Cat(name="c", age=3), return_dtype=pl.Object ).alias("cats") )
这些方法的核心思路都是:让Polars明确知道你要存储的是任意Python对象(用pl.Object dtype标记),而不是它可以自动映射的原生Struct类型,这样它就不会触发自动转换逻辑,而是直接保留你的Pydantic模型实例。
备注:内容来源于stack exchange,提问作者newandlost

