寫了一段時間java切回寫python偶爾會出現(xiàn)一些小麻煩螟凭,比如:在java中自定義對象變成json串很簡單,調(diào)用一個方法就行它呀,但同樣的轉(zhuǎn)換在python中卻不太容易實現(xiàn)螺男。在尋找python自定義對象轉(zhuǎn)json串的過程中,接觸到了猴子補(bǔ)丁這個東西纵穿,感覺還有點意思下隧;本文先實現(xiàn)python自定義對象轉(zhuǎn)json串,再簡單談一下猴子補(bǔ)丁谓媒。
python自定義對象轉(zhuǎn)json串
python自帶的json包不支持自定義對象轉(zhuǎn)json串淆院,在python中用json.dumps轉(zhuǎn)自定義對象時會報異常class is not JSON serializable
,通過增加一段代碼補(bǔ)毒涔摺(稱作猴子補(bǔ)锻帘纭)便可實現(xiàn)自定義轉(zhuǎn)換,補(bǔ)丁代碼如下:
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
同時在自定義對象里面實現(xiàn)to_json方法抢野。
class Tmp:
def __init__(self, id, name):
self.id = id
self.name = name
def to_json():
# 返回自定義對象json串
pass
最后保證補(bǔ)丁代碼在自定義對象轉(zhuǎn)json之前執(zhí)行過一次即可拷淘。
通過補(bǔ)丁代碼我們可以看到,代碼替換了json包的默認(rèn)轉(zhuǎn)json的方法蒙保,運行了補(bǔ)丁代碼后辕棚,轉(zhuǎn)json的過程變成了先找對象的to_json
屬性,在沒有to_json
屬性的情況下才使用默認(rèn)的JSONEncoder.default的方法邓厕,也就是通過這么一個patch逝嚎,增加了json包原來沒有的功能。
猴子補(bǔ)丁
關(guān)于猴子補(bǔ)丁為啥叫猴子補(bǔ)丁详恼,據(jù)說是這樣子的:
這個叫法起源于Zope框架补君,大家在修正Zope的Bug的時候經(jīng)常在程序后面追加更新部分,這些被稱作是“雜牌軍補(bǔ)丁(guerilla patch)”昧互,后來guerilla就漸漸的寫成了gorllia((猩猩)挽铁,再后來就寫了monkey(猴子),所以猴子補(bǔ)丁的叫法是這么莫名其妙的得來的敞掘。
猴子補(bǔ)丁主要有以下幾個用處:
- 在運行時替換方法叽掘、屬性等
- 在不修改第三方代碼的情況下增加原來不支持的功能
- 在運行時為內(nèi)存中的對象增加patch而不是在磁盤的源代碼中增加
例如:上面自定義對象轉(zhuǎn)json,在原有json包不滿足的條件下玖雁,只需要將以上的一個patch寫在一個文件里自己再import一次更扁,便可實現(xiàn)自己想要的功能,這是非常方便的。
可以知道猴子補(bǔ)丁的主要功能便是在不去改變源碼的情況下而對功能進(jìn)行追加和變更浓镜;對于編程過程中使用一些第三方不滿足需求的情況下溃列,使用猴子補(bǔ)丁是非常方便的。
猴子補(bǔ)丁膛薛,算是編程中的一個技巧了听隐。
拓展
json包默認(rèn)轉(zhuǎn)json的過程
可以看一下json包里面轉(zhuǎn)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,將所有的自建對象都匹配一遍哄啄,最后匹配不到的就報錯了雅任,所以自定義對象轉(zhuǎn)json自然會有問題。
其他實現(xiàn)自定義對象轉(zhuǎn)json的方法
其實json包的源碼文檔里面也有很詳細(xì)的別的自定義對象轉(zhuǎn)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]'
'''