首先希望大家明白
Python中的序列化首选从来就不是json, 使用json只是为了与其他应用通信.
Python内部通讯, 对象的序列化请使用pickle, 就像你用C/C++, 基本是不会考虑json的,
仅在内部通讯时, 你该用ProtoBuf.
我遇到的一点问题
Python中免不了也要定义一些类, 自定义的类如果想要序列化, 就是一件麻烦事了.
对于下面这一个类, 如果我们想要格式化某个对象.
1 2 3 4 5 6 7 8 9 10
| class Inner(): def __init__(self): self.m_a = 'a' self.m_b = 'b'
In [7]: i = Inner()
In [8]: json.dumps(i) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)
|
这样我们就会考虑给这个Inner类增加一个函数, 变成类似下面这个样子.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Inner(): """ 一个类 """ def __init__(self): self.m_a = 'a' self.m_b = 'b'
def to_json(self): return json.dumps({ 'm_a': self.m_a, 'm_b': self.m_b, })
In [19]: i.to_json() Out[19]: '{"m_a": "a", "m_b": "b"}'
|
然后, 你觉得世界就变得正常了, 一切的一切就满足了吗.
愿望是极好的, 但是… 正常的生活不是由一个类组成的.
上面的那个类名叫Inner, 那会不会有一个类叫Outer包含它呢, 好像很可能会有吧.
1 2 3 4 5 6 7 8 9 10 11
| class Outer(): def __init__(self): self.m_i = Inner() self.m_c = 'c'
In [21]: o = Outer()
In [22]: json.dumps(o) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)
|
于是你决定也向Outer中再加一个函数, 好像像下面这样也能达到效果.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Outer(): """ 一个类 """ def __init__(self): self.m_i = Inner() self.m_c = 'c'
def to_json(self): return json.dumps({ 'm_i': { 'm_a': self.m_i.m_a, 'm_b': self.m_i.m_b, }, 'm_c': self.m_c })
In [26]: o.to_json() Out[26]: '{"m_i": {"m_a": "a", "m_b": "b"}, "m_c": "c"}'
|
世界好像也不止有两个类吧.
现在, 我们遇到了一个问题, 再出现类似的情况怎么办, 我们要一层一层的去包裹.
而且是纯手动, 你们能想想这个过程有多么的特别吗, 而且每个对象需要调用
to_json
这一类函数, 的确很难.
这个时候, 你该考虑Google, StackOverflow了, 也许有人曾经遇到过这样的问题,
我搜索的是python json dumps custome object
这样的一串关键字.
How to make a class JSON serializable是第一个内容, 里面介绍了一些方法.
例如:
1 2 3 4 5 6 7 8
| class CustomJsonEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Outer): return {'m_c': obj.m_c} return json.JSONEncoder.default(self, obj)
In [32]: json.dumps(o, cls=CustomJsonEncoder) Out[32]: '{"m_c": "c"}'
|
借助这个方式, 我们可以更新原来的Outer与Inner类, 再与CustomJsonEncoder结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Inner(): """ 一个类 """ def __init__(self): self.m_a = 'a' self.m_b = 'b'
def to_json(self): return { 'm_a': self.m_a, 'm_b': self.m_b, }
class Outer(): """ 一个类 """ def __init__(self): self.m_i = Inner() self.m_c = 'c'
def to_json(self): return { 'm_i': self.m_i, 'm_c': self.m_c }
class CustomJsonEncoder(json.JSONEncoder): def default(self, obj): if getattr(obj, "to_json", None): return obj.to_json() return json.JSONEncoder.default(self, obj)
In [35]: o = Outer()
In [36]: json.dumps(o, cls=CustomJsonEncoder) Out[36]: '{"m_i": {"m_a": "a", "m_b": "b"}, "m_c": "c"}'
|
你别说, 还是挺成功的. 这样一来, 自己的定义的类也可以实现to_json
这个方法,
缺点就是每次json.dumps
的时候需要加上后面这一串了cls=CustomJsonEncoder
能不能再优雅一点
json.dumps(o, cls=CustomJsonEncoder)
这句话真的挺长的, 如果我们能简化
一点就好了, 还像原来那样json.dumps(o)
多好.
我是比较幸运的, 又找到了How to make a class JSON serializable. 里面提供
了一种思路, 由于json.dumps
时, 默认的JSONEncoder是可以被我们找到的.
既然如此, 将它替换也是可能的, 这是原文中的代码(原文中有其他方法, 也可以去读读看).
1 2 3 4 5 6 7
| from json import JSONEncoder
def _default(self, obj): return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder().default JSONEncoder.default = _default
|
我在自己的项目中, 换用了另外一种形式, 我希望原始的default最先被调用, 而后才是
我们自定义的to_json
, 另外对于项目中的Decimal对象, 原始的函数似乎也不能用,
我就只能单独放出来了.
1 2 3 4 5 6 7 8 9 10 11 12 13
| def _default(self, obj): """ 以补丁的形式重写了json.JSONEncoder.default函数 避免在每个json.dumps处修改 """ if isinstance(obj, Decimal): return float(obj)
try: orig_default(self, obj) except: return getattr(obj.__class__, "to_json")(obj)
json.JSONEncoder.default = _default
|
** << Dive Into Python >> **
在Python中没有常量, 如果你试图努力的话什么都可以改变.
这一点满足Python的核心原则之一: 坏的行为应该被克服而不是被取缔.
你们认为这个方法怎么样?
好处是, 你可以将cls=CustomJsonEncoder
去掉了.
而随之而来的坏处就是, 你的程序修改了原始的JSONEncoder中的default
, 你看我在我
上面的程序中, 尽最大努力让程序先调用原来的default, 即便这样, Decimal对象在
序列化时仍然出现了问题, 不得不单独提出来.
另外一个问题就是, 你每定义一个类, 就意味着你需要实现一个to_json
方法,
这种隐式的约定, 永远是可能出现问题的地方.
所幸, 我们平时使用也应该使用的是pickle, 而不是json.
json应该只在这一种情况下使用: 你想要与其他语言的程序通讯时.