psycopg2插入含NaN的numpy数组报错,求正确实现方法
含NaN的NumPy数组插入PostgreSQL表的问题与解决
问题背景
最初的适配器代码可以正常插入多维NumPy数组:
def adapt_numpy_array(numpy_array): return AsIs(numpy_array.tolist()) register_adapter(np.ndarray, adapt_numpy_array) register_adapter(np.int32, AsIs) register_adapter(np.int64, AsIs) register_adapter(np.float64, AsIs)
但当数组包含NaN时,触发PostgreSQL错误:
psycopg2.errors.UndefinedColumn: FEHLER: Spalte »nan« existiert nicht
LINE 2: ...6000.0, 692000.0, 732000.0, 830000.0, 928000.0], [nan, nan, ..
修改适配器后:
def adapt_numpy_array(numpy_array): return numpy_array.tolist() def nan_to_null(f, _NULL=psycopg2.extensions.AsIs('NULL'), _Float=psycopg2.extensions.Float): if not np.isnan(f): return _Float return _NULL register_adapter(float, nan_to_null)
又出现新错误:
AttributeError: 'list' object has no attribute 'getquoted'
报错原因
- 第一个错误:用
AsIs直接将NumPy数组转成列表插入时,PostgreSQL会把nan当成列名——因为它没有被转成SQL的NULL,而是作为字符串"nan"被拼接进SQL语句,数据库误以为这是一个不存在的列。 - 第二个错误:修改后的
adapt_numpy_array直接返回原生Python列表,但psycopg2没有内置的列表适配器(除非是特定数组类型的适配逻辑),它期望适配器返回实现了getquoted方法的psycopg2扩展对象(比如AsIs、Float),而非原生列表。另外,注册的float适配器只处理单独的float对象,psycopg2不会自动递归遍历列表去应用元素适配器。
正确解决方案
要同时处理多维数组和NaN,需要递归处理数组结构,把NaN替换为SQL的NULL,同时确保返回psycopg2能识别的适配对象。以下是两种可行方案:
方案一:递归生成合法SQL数组字符串
import numpy as np import psycopg2 from psycopg2.extensions import AsIs, register_adapter def numpy_to_sql_array(arr): # 递归处理多维数组 if isinstance(arr, np.ndarray): return "ARRAY[" + ", ".join(numpy_to_sql_array(x) for x in arr) + "]" elif isinstance(arr, float) and np.isnan(arr): return "NULL" elif isinstance(arr, (np.number, int, float)): return str(arr) else: return str(arr) def adapt_numpy_array(numpy_array): return AsIs(numpy_to_sql_array(numpy_array)) # 注册各类适配器 register_adapter(np.ndarray, adapt_numpy_array) register_adapter(np.int32, AsIs) register_adapter(np.int64, AsIs) register_adapter(np.float64, lambda x: AsIs(str(x)) if not np.isnan(x) else AsIs("NULL"))
该方案将NumPy数组递归转换为PostgreSQL可识别的ARRAY[]格式字符串,同时把NaN替换为NULL,再用AsIs直接传入SQL语句。
方案二:利用psycopg2的adapt函数处理列表
import numpy as np import psycopg2 from psycopg2.extensions import register_adapter, AsIs, Float def nan_to_null(f): if isinstance(f, float) and np.isnan(f): return AsIs("NULL") # 非NaN数值用默认Float适配器处理 return Float(f) # 注册各类数值类型适配器 register_adapter(float, nan_to_null) register_adapter(np.int32, lambda x: AsIs(str(x))) register_adapter(np.int64, lambda x: AsIs(str(x))) register_adapter(np.float64, nan_to_null) def adapt_numpy_array(numpy_array): # 转成Python列表后,用psycopg2的adapt处理整个列表 return psycopg2.extensions.adapt(numpy_array.tolist()) register_adapter(np.ndarray, adapt_numpy_array)
该方案借助psycopg2的adapt函数处理列表,它会遍历列表元素并应用对应的适配器,将NaN转为NULL,同时把整个列表正确转成SQL数组。
关键注意点
- psycopg2不会自动递归处理容器(如列表、数组)内的元素,必须确保容器本身被适配,或元素的适配器能被正确应用。
- 不能直接返回原生列表给psycopg2,必须返回其支持的适配对象(如
AsIs、adapt生成的对象)。 - PostgreSQL没有
NaN概念,必须将NumPy的NaN转换为SQL的NULL才能正确插入。
内容的提问来源于stack exchange,提问作者Tengrath
相关产品推荐
相关产品推荐

