orient='records'将dataframe每行转为字典,输出list[dict],忽略索引、保留列名作key,nan保持为nan,不兼容json;需预处理时间戳、nan等类型,大批量时可用itertuples或to_json替代。

orient='records' 是最接近“每行转字典”的直觉用法
它把 DataFrame 的每一行变成一个独立的 dict,整个结果是 list[dict]。适合写入 JSON、传给 API、批量插入数据库这类场景——你不需要按列聚合,只关心“这一条数据长什么样”。
常见错误是误以为它会保留索引或自动加 ID 字段:to_dict(orient='records') 完全忽略索引,只取列名作 key、对应值作 value。
- 如果某列含
NaN,对应字典里该 key 的值就是nan(不是None,注意 JSON 序列化会报错) - 列名必须是合法的 Python 标识符或字符串;若含空格或特殊符号,照样照搬进 key,后续用
dict['col name']访问即可 - 性能上比
orient='dict'稍慢,但对万级以下数据无感;真正卡顿往往来自后续的json.dumps()或循环处理
别和 orient='list'、orient='dict' 混了
orient='list' 输出是 {'col1': [val1, val2], 'col2': [val3, val4]},整列打包——适合做批量绘图数据源;orient='dict' 是 {'col1': {0: val1, 1: val2}, ...},带索引映射——适合按列查某行某值。这两个和 'records' 的数据结构完全不兼容,强行混用会导致 KeyError 或类型错误。
典型翻车现场:df.to_dict(orient='records')[0]['missing_col'] 报错,其实只是列名拼错了,但你会先怀疑 orient 参数没生效。
- 检查列名是否真实存在:用
df.columns.tolist()对照 - 避免链式调用陷阱:不要写
df.dropna().to_dict(orient='records')[0],万一 drop 后为空,直接 IndexError -
orient='index'更危险:它用原索引当 key,值才是 dict,新手常误以为是“按行转”,实际结构是{0: {...}, 1: {...}}
遇到时间戳、分类、嵌套对象容易崩
to_dict(orient='records') 对 datetime64 列默认转成 Timestamp 对象,JSON 序列化直接抛 TypeError: Object of type Timestamp is not JSON serializable;category 类型会退化成原始值(通常没问题),但若含 NaN 就可能变 None;自定义类或 pd.Interval 更是直接报错。
- 时间列务必先转字符串:
df.assign(time=df['time'].dt.strftime('%Y-%m-%d %H:%M:%S'))再to_dict - 不想改原 df?用
df.astype({'time': str}),但注意时区信息会丢失 - 含
NaN的列想统一成None(适配 JSON):提前df = df.where(pd.notnull(df), None)
真要高性能大批量转换,别只盯着 orient 参数
当 DataFrame 超过 10 万行,to_dict(orient='records') 的内存和速度瓶颈就明显了。它本质是 Python 层逐行构造字典,没法绕过 GIL。
- 纯数值列优先考虑
df.values.tolist()+ 手动 zip 列名,快 3–5 倍,但失去类型信息(全变 float/int) - 用
df.itertuples(index=False, name=None)配合列表推导,比to_dict略快且可控,例如:[dict(zip(df.columns, row)) for row in df.itertuples(index=False)] - 如果最终要 JSON,直接
df.to_json(orient='records')更省事,底层走 C 实现,还不占额外内存
很多人卡在“怎么让字典里的数字不带小数点”,其实是 float 列本身的问题,跟 to_dict 无关——得提前用 astype(int) 或 round() 处理,否则字典里还是 3.0。










