使用pickle.dumps序列化numpy数组存PostgreSQL报ndarray非C连续错误
错误成因
你遇到的报错不是pickle.dumps触发的,根源出在三个地方:
- 链式索引赋值失效:你用
Data2_mcw_conv[col][row] = value这种链式索引方式赋值,pandas不会保证修改原DataFrame的实际值,大概率你处理后的序列化bytes根本没有写入DataFrame,单元格里保留的还是原来的非C连续numpy数组。 - 裸except吞掉了所有异常:你没有给try加具体的异常捕获类型,一旦序列化环节出现任何未知错误,都会直接进入except分支,如果当前列第一个元素不是datetime类型,你就会直接把原始的非C连续数组写回单元格,完全覆盖了你之前的转换逻辑。
- 最终报错是
to_sql阶段触发的:你给numpy数组列指定了BYTEA类型,sqlalchemy在往PostgreSQL写BYTEA字段时,遇到单元格里残留的numpy数组,会尝试将数组直接转成二进制缓冲区,这一步会要求数组是C连续的,就触发了你看到的报错。
解决方法
你可以按照下面的逻辑修改代码:
- 提前判定每一列的类型,不要在循环里逐行重复判断
- 替换链式索引为
.at/.loc的正确赋值方式,或者直接用apply做整列向量化转换,保证修改生效 - 去掉裸except,只捕获需要处理的异常,避免错误被吞
修改后的参考代码
import pickle import numpy as np import pandas as pd from sqlalchemy.types import BYTEA, TIMESTAMP import datetime dtypedict = {} Data2_mcw_conv = Data2_mcw.copy() # 提前判定列类型,整列转换 for col in Data2_mcw_conv.columns: first_val = Data2_mcw_conv[col].iloc[0] if type(first_val).__module__ == np.__name__: dtypedict[col] = BYTEA # 整列转换,不需要逐行循环,效率更高也不会出现赋值失效问题 Data2_mcw_conv[col] = Data2_mcw_conv[col].apply(lambda x: pickle.dumps(x.copy(order='C'))) elif isinstance(first_val, datetime.date): dtypedict[col] = TIMESTAMP # 写入数据库 Data2_mcw_conv.to_sql( name='data2_mcw_tmp', con=conn, if_exists='replace', dtype=dtypedict, index=False )
可选优化方案
如果你需要更高的序列化性能,也可以直接用numpy自带的tobytes()方法序列化,反序列化的时候用np.frombuffer()指定dtype和shape恢复即可,比pickle的效率更高,存储空间也更小,仅需要额外存储数组的dtype和shape信息,你可以根据自己的需求选择。
内容的提问来源于stack exchange,提问作者xyiong
相关产品推荐
相关产品推荐

