You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Psycopg3的COPY语句中将NaN转换为null?

在Psycopg3中实现NaN转数据库NULL的方案

背景:Psycopg2的实现方式

在Psycopg2中,可通过以下自定义适配器将Python的NaN值转换为PostgreSQL的NULL:

__REGISTERED = False

def _nan_to_null(f,
        _NULL=psycopg2.extensions.AsIs('NULL'),
        _Float=psycopg2.extensions.Float):
    if not np.isnan(f):
        return _Float(f)
    return _NULL

def register_nan_adapter():
    global __REGISTERED
    if not __REGISTERED:
        print('Register nan to null adapter for psycopg2...')
        psycopg2.extensions.register_adapter(float, _nan_to_null)
        __REGISTERED = True
    else:
        print('nan to null adapter for psycopg2 is already registered!')

问题:Psycopg3中的适配难题

尝试在Psycopg3中通过自定义FloatDumper实现相同功能时,最初的代码无法生效:

class NullNan(FloatDumper):
    def dump(self, elem):
        if np.isnan(elem):
            return b"NULL"
        else:
            return super().dump(elem)

connection.adapters.register_dumper(float, NullNan)

后续调整为返回None后,普通的插入、更新操作可正常工作,但在执行COPY语句时触发异常:

问题代码

class NullNan(FloatDumper):
    def dump(self, elem):
        if np.isnan(elem):
            return None
        else:
            return super().dump(elem)

with cursor.copy(f"COPY _r ({col_names_str}) FROM STDIN") as copy:
                for index, row in df.iterrows():
                    rec = row.values.tolist()
                    copy.write_row(rec)

报错信息

psycopg.errors.QueryCanceled: COPY from stdin failed: error from Python: TypeError - expected string or bytes-like object

解决方案

方案1:修改自定义Dumper适配COPY场景

COPY命令对NULL的处理逻辑和普通SQL操作不同,需要返回PostgreSQL COPY格式专用的b'\\N'来表示NULL。修改后的Dumper代码如下:

from psycopg.types.numeric import FloatDumper
import numpy as np

class NullNan(FloatDumper):
    def dump(self, elem):
        if np.isnan(elem):
            # 普通SQL操作返回None,COPY操作返回专用的NULL标识
            if self.ctx.format is None:
                return None
            else:
                return b'\\N'
        else:
            return super().dump(elem)

# 注册自定义Dumper
connection.adapters.register_dumper(float, NullNan)

方案2:提前处理行数据

在生成COPY的行数据时,直接将NaN替换为None,跳过Dumper的适配逻辑:

import numpy as np

with cursor.copy(f"COPY _r ({col_names_str}) FROM STDIN") as copy:
    for index, row in df.iterrows():
        # 遍历行数据,将所有NaN替换为None
        rec = [
            None if isinstance(x, float) and np.isnan(x) 
            else x 
            for x in row.values.tolist()
        ]
        copy.write_row(rec)

两种方案均可解决COPY语句的报错问题,同时保证插入、更新操作正常将NaN转换为数据库NULL。

内容的提问来源于stack exchange,提问作者Andrey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 03:05:19