Python 猴子补丁(Monkey Patch)实战:自定义对象转 JSON
本文介绍 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]'
'''
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









