Python 猴子补丁(Monkey Patch)实战:自定义对象转 JSON

不晚
不晚
发布时间:2026-10-13 | 2人浏览过
原创

本文介绍 Python 中的猴子补丁(Monkey Patch)技术,重点演示如何通过修改 json 模块的默认编码器,实现自定义对象到 JSON 字符串的无缝转换。同时解析猴子补丁的命名由来、核心应用场景及注意事项,帮助开发者在不修改源码的前提下灵活扩展第三方库功能。

Python 猴子补丁实战:自定义对象转 JSON

从 Java 开发转回 Python 开发时,常会遇到一些细微的习惯差异。例如在 Java 中,将自定义对象序列化为 JSON 字符串通常只需调用一个简单方法即可;但在 Python 中,这一过程却没那么直观。在探索 Python 自定义对象转换 JSON 的过程中,我接触到了“猴子补丁”这一概念,觉得颇具趣味。本文将首先演示如何利用猴子补丁实现自定义对象转 JSON,随后简要探讨猴子补丁的相关概念。

Python 自定义对象转 JSON

Python 标准库中的 json 模块默认不支持直接序列化自定义对象。若直接使用 json.dumps 处理自定义对象,会抛出“class is not JSON serializable”的异常。通过添加一段代码补丁(即猴子补丁),即可实现自定义对象的转换。补丁代码如下:


from json import JSONEncoder
  def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)
  _default.default = JSONEncoder().default
  default.JSONEncoder.default = _default

同时,需要在自定义对象类中实现 to_json 方法。


class Tmp:
  def __init__(self, id, name):
    self.id = id
    self.name = name

  def to_json():
    # 返回自定义对象json串
    pass

最后,确保在执行自定义对象转 JSON 之前,补丁代码已被执行一次即可生效。

从补丁代码可以看出,它替换了 json 模块默认的 JSON 转换逻辑。执行补丁后,序列化过程变为:优先查找对象的 to_json 属性;若不存在该属性,则回退使用默认的 JSONEncoder.default 方法。通过这种 patch 机制,成功为 json 模块增加了原本不具备的功能。

猴子补丁的概念与由来

猴子补丁

关于“猴子补丁”这一名称的由来,据说如下:

该术语起源于 Zope 框架。在修复 Zope 的 Bug 时,开发者常在程序末尾追加更新代码,这类补丁被称为“游击队补丁(guerilla patch)”。随着时间推移,“guerilla”逐渐被误写为“gorllia”(大猩猩),最终演变为“monkey”(猴子)。因此,“猴子补丁”这一名称便如此莫名其妙地流传开来。

猴子补丁主要有以下几个用途:

  • 在运行时替换方法、属性等
  • 在不修改第三方代码的情况下,增加原本不支持的功能
  • 在运行时为内存中的对象添加补丁,而非修改磁盘上的源代码

例如:前述自定义对象转 JSON 的场景。在原有 json 包不满足需求时,只需将上述 patch 代码写入一个文件并自行 import 一次,即可实现所需功能,非常便捷。

由此可见,猴子补丁的核心功能是在不改变源码的前提下,对功能进行追加和变更。当使用某些第三方库无法满足需求时,采用猴子补丁是一种高效的解决方案。

猴子补丁可视为编程中的一种技巧。

拓展:JSON 处理机制与替代方案

拓展

json 包默认转换 JSON 的过程

我们可以查看 json 模块内部转换 JSON 字符串的流程:


 def _iterencode(o, _current_indent_level):
    if isinstance(o, basestring):
      yield _encoder(o)
    elif o is None:
      yield 'null'
    elif o is True:
      yield 'true'
    elif o is False:
      yield 'false'
    elif isinstance(o, (int, long)):
      yield str(o)
    elif isinstance(o, float):
      yield _floatstr(o)
    elif isinstance(o, (list, tuple)):
      for chunk in _iterencode_list(o, _current_indent_level):
        yield chunk
    elif isinstance(o, dict):
      for chunk in _iterencode_dict(o, _current_indent_level):
        yield chunk
    else:
      if markers is not None:
        markerid = id(o)
        if markerid in markers:
          raise ValueError("Circular reference detected")
        markers[markerid] = o
      o = _default(o)
      for chunk in _iterencode(o, _current_indent_level):
        yield chunk
      if markers is not None:
        del markers[markerid]

其本质是一连串的 if-elif-else 判断,逐一匹配所有自建对象类型。若最终无法匹配,则报错。因此,自定义对象直接转 JSON 自然会引发问题。

其他实现自定义对象转 JSON 的方法

实际上,json 包的源码文档中也详细列出了其他处理自定义对象转 JSON 的方法。


r'''
Specializing JSON object decoding::

  >>> import json
  >>> def as_complex(dct):
  ...   if '__complex__' in dct:
  ...     return complex(dct['real'], dct['imag'])
  ...   return dct
  ...
  >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
  ...   object_hook=as_complex)
  (1+2j)
  >>> from decimal import Decimal
  >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
  True

Specializing JSON object encoding::

  >>> import json
  >>> def encode_complex(obj):
  ...   if isinstance(obj, complex):
  ...     return [obj.real, obj.imag]
  ...   raise TypeError(repr(o) + " is not JSON serializable")
  ...
  >>> json.dumps(2 + 1j, default=encode_complex)
  '[2.0, 1.0]'
  >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
  '[2.0, 1.0]'
  >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
  '[2.0, 1.0]'
'''

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表