專 欄
❈
正小歪,Python 工程师,主要负责 Web 开发和日志数据处理。博客文章《真正的 Tornado 异步非阻塞》、《
使用 JWT 让你的 RESTful API 更安全》等多次入选知名技术社区每日精选。《
使用 Shipyard 搭建 Docker 集群》被选入 Dockerone 周报。
个人博客: https://www.hexiangyu.me
GitHub: https://github.com/zhengxiaowai❈
在用Python编程时很经常做的一件事就是 Python 数据类型和 JSON 数据类型的转换。
但是存在一个明显的问题,JSON 作为一种数据交换格式有固定的数据类型,但是 Python 作为编程语言除了内置的数据类型以外还能编写自定义的数据类型。
墙裂推荐:去看看 JSON 官网对 JSON 的介绍:http://www.json.org/json-zh.html
比如你肯定遇到过类似的问题:
那么问题就来了,如何把各种各样的 Python 数据类型转化成 JSON 数据类型。
一种很不 pythonic 的做法就是,先转换成某种能和 JSON 数据类型直接转换的值,然后在 dump,这么做很直接很暴力,但是在各种花式数据类型面前就很无力。
Google 是解决问题的重要方式之一,当你一顿搜索过后,你就会发现其实可以在 dumps 时 encode 这个阶段对数据进行转化。
所以你肯定是那么做的,完美地解决了问题。
JSON 的 Encode 过程
文中代码摘自 https://github.com/python/cpython
删除了几乎所有的 docstring,由于代码太长,直接截取了重要片段。可以在片段最上方的链接查看完整的代码。
熟悉 json 这个库的都知道基本只有4个常用的 API,分别是 dump、dumps 和 load、loads。
源码位于 cpython/Lib/json 中
直接看到最后的 return。可以发现如果不提供 cls 默认就使用 JSONEncoder,然后调用该类的实例方法 encode。
encode 方法也十分简单:
可以看出最后的我们得到 JSON 都是 chunks 拼接得到的,chunks 是调用 self.iterencode 方法得到的。
iterencode 方法比较长,我们只关心最后几行。
返回值 _iterencode
,是函数中 c_make_encoder
或者 _make_iterencode
这两个高阶函数的返回值。
c_make_encoder
是来自 _json
这个 module ,这个 module 是一个 c 模块,我们不去关心这个模块怎么实现的。
转去研究同等作用的 _make_iterencode
方法。
同样需要关心的只有返回的这个函数,代码里各种 if-elif-else 逐一把内置类型转换成 JSON 类型。
在对面无法识别的类型时候就使用了 _default()
这个方法,然后递归调用解析各个值。
_default
就是最前面那个被覆盖的 default
。
到这里就可以完全了解 Python 是如何 encode 成 JSON 数据。
总结一下流程,json.dumps()
调用 JSONEncoder 的实例方法 encode()
,随后使用 iterencode()
递归转化各种类型,最后把 chunks 拼接成字符串后返回。
优雅的解决方案
通过前面的流程分析之后,知道为什么继承 JSONEncoder 然后覆盖 default 方法就可以完成自定义类型解析了。
也许你以后需要解析 datetime 类型数据,你可定会那么做:
最后调用父类是 default()
方法纯粹是为了触发异常。
Python 可以使用 singledispatch 来解决这种单泛型问题。
这种写法比较符合设计模式的规范。假如以后有了新的类型,不用再修改ExtendJSONEncoder
类,只需要添加适当的 singledispatch 方法就可以了, 比较 pythonic 。
如果你执意的想在类中添加 singledispatch 可以参考:https://stackoverflow.com/a/24602374/5227020 ,当然我仍然觉得还是不要写在类中比较好。
长按扫描关注Python中文社区,
获取更多技术干货!
Python 中 文 社 区
Python中文开发者的精神家园
合作、投稿请联系微信:
pythonpost
— 人生苦短,我用Python —
1MEwnaxmMz7BPTYzBdj751DPyHWikNoeFS