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

Python2中自定义JSONEncoder传参异常排查及Python3差异分析

问题原因与解决办法

你遇到的问题核心是Python 2中json.JSONEncoder的__init__方法签名和你的自定义编码器参数冲突了。

为什么type会被赋值给skipkeys?

在Python 2里,json.JSONEncoder.__init__的签名是这样的:

def __init__(self, skipkeys=False, ensure_ascii=True, check_circular=True,
             allow_nan=True, sort_keys=False, indent=None, separators=None,
             encoding='utf-8', default=None):

它的第一个位置参数(除了self)是skipkeys。而你定义的MyEncoder.__init__把type作为第一个参数,当你调用json.dump(data, fp, type, cls=MyEncoder)时,你传入的type值会被当作位置参数传递给MyEncoder的__init__,也就是self之后的第一个参数——而这个位置恰好对应父类的skipkeys参数。

所以当你调用super(MyEncoder, self).__init__(**kwargs)时,父类会把skipkeys设置为你传入的type值,同时你虽然给self.type赋值了,但父类的初始化逻辑已经把这个值关联到了skipkeys属性上,这就是你看到type跑到kwargs['skipkeys']里的本质原因。

怎么解决这个问题?

你需要避免自定义参数和父类的位置参数冲突,推荐两种做法:

方法1:将自定义参数改为关键字参数传递(推荐)

修改你的编码器,让type只能通过关键字参数传入,同时在__init__里先提取这个参数,再把剩余的kwargs传给父类:

import json
from datetime import datetime

class MyEncoder(json.JSONEncoder):
    def __init__(self, **kwargs):
        # 从kwargs中取出type参数,可根据需求设置默认值
        self.type = kwargs.pop('type', None)
        super(MyEncoder, self).__init__(**kwargs)
    
    def default(self, obj):
        if isinstance(obj, datetime):
            # 这里就可以用self.type来做分支处理了
            if self.type == 'timestamp':
                return obj.timestamp()
            elif self.type == 'iso':
                return obj.isoformat()
            # 其他自定义处理逻辑
        return super(MyEncoder, self).default(obj)

调用时必须用关键字参数传type:

json.dump(data, fp, cls=MyEncoder, type='timestamp')

方法2:调整参数顺序,让自定义参数在父类所有参数之后

如果你坚持想用位置参数(不推荐,可读性差),可以把type放在父类所有参数的后面:

class MyEncoder(json.JSONEncoder):
    def __init__(self, skipkeys=False, ensure_ascii=True, check_circular=True,
                 allow_nan=True, sort_keys=False, indent=None, separators=None,
                 encoding='utf-8', default=None, type=None):
        super(MyEncoder, self).__init__(skipkeys, ensure_ascii, check_circular,
                                       allow_nan, sort_keys, indent, separators,
                                       encoding, default)
        self.type = type
    
    # default方法同上

调用时要确保type在所有父类参数之后,不过这种方式太繁琐,维护性差,不推荐使用。

Python 3中的差异

Python 3对json.JSONEncoder.__init__的签名做了优化,引入了强制关键字参数:

def __init__(self, *, skipkeys=False, ensure_ascii=True, check_circular=True,
             allow_nan=True, sort_keys=False, indent=None, separators=None,
             default=None):

注意参数列表开头的*,这表示self之后的所有参数都必须是关键字参数。

这意味着如果你在Python 3里还是写__init__(self, type, **kwargs),然后用json.dump(data, fp, type, cls=MyEncoder)这种位置参数的方式传type,会直接抛出TypeError,因为父类的__init__不接受位置参数(除了self)。

所以在Python 3里,必须用关键字参数传递自定义参数,也就是上面的方法1是唯一合理的选择,这也从根源上避免了参数冲突的问题,代码逻辑更清晰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:13:56