请注意, 这里承接了上一篇博客: Python编码问题的一点理解, 因此Python2的代码中统一使用unicode, 对此不理解的同学, 请花点时间读一下第一篇博客.

为什么json.dumps出来的数据没法打印?

以Python2为例

1
2
3
4
5
6
7
8
9
# In python2
import json
info = {'err_msg': u'中文字符'}

json.dumps(info) # 这种形式使用print或是其他方式打印是无法正常显示中文的
# Out[37]: '{"err_msg": "\\u4e2d\\u6587\\u5b57\\u7b26"}'

print(json.dumps(info)) # 想要打印, 结果却是这样的
# {"err_msg": "\u4e2d\u6587\u5b57\u7b26"}

上网搜了下, 当然也有解决办法的:

1
2
3
4
5
json.dumps(info, ensure_ascii=False)  # 增加ensure_ascii之后的结果
# Out[36]: u'{"err_msg": "\u4e2d\u6587\u5b57\u7b26"}'

print(json.dumps(info, ensure_ascii=False))
# {"err_msg": "中文字符"}

相信大家会产生下面三个问题, 带着这三个问题, 我分别来介绍:

  • 为什么加入ensure_ascii=False就解决了呢?
  • 这两种dumps形式得到的不同的数据还能再json.loads回去吗?
  • 既然有两种dumps形式, 我们该用哪一种呢?

ensure_ascii的作用是什么?

在Python2的官方文档中给了这么一句

If ensure_ascii is true (the default), all non-ASCII characters in the output are escaped with \uXXXX sequences, and the result is a str instance consisting of ASCII characters only. If ensure_ascii is false, some chunks written to fp may be unicode instances. This usually happens because the input contains unicode strings or the encoding parameter is used. Unless fp.write() explicitly understands unicode (as in codecs.getwriter()) this is likely to cause an error.

粗浅的翻译如下:

如果ensure_ascii为true, 所有的非ASCII字符将会以\uXXXX序列的形式打印, 结果将会以str(在Python2中为bytes) 的形式组成. 如果ensure_ascii为false, 得到的将会是unicode对象. 这种形式会发生, 因为输入中包含了unicode字符或是编码时(json.loads时)指定了该参数. 如果fp.writer不能显式的理解unicode, 那么这样转化会造成一种错误.

有以下2点区别:

  1. 返回值的属性不同: ensure_ascii为true, 返回bytes; 为false, 返回unicode
  2. 返回值不同, ensure_ascii为true时, unicode字符对应\\u7b26这样的形式, unicode被完全分解, 由ascii组成, 当ensure_ascii为false是, \u7b26仍然表示一个unicode字符

两种形式是否都可以重新编码为字典?

既然能够产生两种字符串, 那么自然会有人问, 这两种字符串是不是都能通过编码操作 得到原字符串, 答案是是的, 这两种形式都可以用json.loads回溯

1
2
3
4
5
6
json.loads('{"err_msg": "\\u4e2d\\u6587\\u5b57\\u7b26"}')
# Out[15]: {u'err_msg': u'\u4e2d\u6587\u5b57\u7b26'}


json.loads(u'{"err_msg": "中文字符"}')
# Out[14]: {u'err_msg': u'\u4e2d\u6587\u5b57\u7b26'}

最骚的是, 这两种形式重新编码后, 结果还是一样的

我们该如何选择

我根据自己的情况, 给大家两点建议:

  1. 涉及到日志打印, 获取与其他类库的交流, 用ensure_ascii=False的形式, 这样可以保证最后所得的字符串一定是unicode形式, 在整个程序中保持统一
  2. 类内部的数据交流, 可以使用缺省的行为, 因为loads是可以变回去的.

附录: Python2, Python3的异同

先说结论: 只要你代码中确保使用的是unicode(在Python2中), 那么都可以得到基本一致的结果, 请确保字典中所有简单数据均使用unicode的形式.

两种dump形式

1
2
3
4
5
6
7
8
9
# In python2
import json
info = {'err_msg': u'中文字符'}

json.dumps(info)
# Out[37]: '{"err_msg": "\\u4e2d\\u6587\\u5b57\\u7b26"}'

json.dumps(info, ensure_ascii=False)
# Out[36]: u'{"err_msg": "\u4e2d\u6587\u5b57\u7b26"}'
1
2
3
4
5
6
7
# In python3, 程序中没有了unicode的形式, 不过普通的dumps得到的数据仍然是无法打印的

json.dumps(info)
# Out[17]: '{"err_msg": "\\u4e2d\\u6587\\u5b57\\u7b26"}'

json.dumps(info, ensure_ascii=False)
# Out[18]: '{"err_msg": "中文字符"}'

两种形式的字符串重新loads

1
2
3
4
5
6
7
# In python2

json.loads('{"err_msg": "\\u4e2d\\u6587\\u5b57\\u7b26"}')
# Out[3]: {u'err_msg': u'\u4e2d\u6587\u5b57\u7b26'}

json.loads(u'{"err_msg": "\u4e2d\u6587\u5b57\u7b26"}')
# Out[4]: {u'err_msg': u'\u4e2d\u6587\u5b57\u7b26'}
1
2
3
4
5
6
# In python3, 与Python2行为一致, 依旧可以复原
json.loads('{"err_msg": "\\u4e2d\\u6587\\u5b57\\u7b26"}')
# Out[2]: {'err_msg': '中文字符'}

json.loads('{"err_msg": "中文字符"}')
# Out[4]: {'err_msg': '中文字符'}