在Python中使用JSON时的注意事项
这篇文章主要介绍在Python中使用JSON时的注意事项,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
在 Python 中使用 JSON 时需要注意的编码问题
写这篇文章的缘由是我使用 reqeusts 库请求接口的时候, 直接使用请求参数里的 json 字段发送数据, 但是服务器无法识别我发送的数据,
排查了好久才知道 requests 内部是使用 json.dumps 将字符串转成 json 的, 而 json.dumps 默认情况下会将 非ASCII 字符转义, 也就是
我发送数据中的中文被转义了, 所以服务器无法识别. 这篇文章虽然是 json.dumps 问题的总结, 但也会涉及到 字符编码 问题, 所以就简单
先说一下 字符编码.
Python 中的字符编码
在 Python3 中, 字符 在内存中是使用 Unicode 存储的, 常规的字符使用 两个字节 表示, 一些很生僻的字符就需要 四个字节. 默认使用
Unicode 存储是什么意思呢, 那就是例子来解释一下, 在 Python Shell 中输入以下字符串 '\u4e2d\u6587', 观察其输出:
In[51]:'\u4e2d\u6587'Out[51]:'中文'
输出的为 中文 两个字. 其实 \u4e2d 和 \u6587 分别表示 中 和 文 的 Unicode 编码(术语称为 码点)的 十六进制 表示, 在 Python3 中
以 \u 开头的字符串被解析为 Unicode 字符, 然后通过其十六进制 码点 解析出具体的字符, 所以 中文 的内存表示即为 \u4e2d\u6587.
获取字符 Unicode 码点
标准库提供了ord函数获取一个字符的Unicode码点, 使用chr函数将码点转换成字符, 下面是示例:
In[54]:ord('中')Out[54]:20013In[56]:chr(20013)Out[56]:'中'
输出的码点是使用十进制表示的, 可以使用以下代码将十进制数字格式化成十六进制字符串:
'{0:04x}'.format(20013)
使用 json.dumps
有了前面的铺垫, 就可以来说说json.dumps了. 下面以一个例子展开:
In[121]:json.dumps('中文',ensure_ascii=True)Out[121]:'"\\u4e2d\\u6587"'In[122]:json.dumps('中文',ensure_ascii=False)Out[122]:'"中文"'
可以看到, 在ensure_ascii为True的情况下,中文被编码成了Unicode码, 为False才能正常显示, 但参数名为什么叫ensure_ascii呢?
来看一下官方文档对这个参数的解释:
如果ensure_ascii是true(即默认值),输出保证将所有输入的非ASCII字符转义。如果ensure_ascii是false,这些字符会原样输出。
现在稍微明白了, 在 ensure_ascii 为 True 的情况下, 如果字符串中存在 非ASCII 字符就将其转义, 根据结果可以知道这个字符被转义为
Unicode 编码并格式化成了一个字符串, 注意 "\\u4e2d\\u6587" 与 "\u4e2d\u6587" 是不同的, 前者是长度为 12 的字符串, 后者则会被
Python 直接解析为 中文, 长度为 2. 这也就是我一开始出现的问题, 直接将转义的字符串在网络上传输可能会无法被识别. 比如 中文 被转
义成 \\u4e2d\\u6587, 而服务器如果不知道它是被转义过的字符串, 那它就是一个长度为 12 的普通字符串, 肯定会识别出错. 而将
ensure_ascii 设为 False 就不会进行转义, 使用原始字符.
识别转义字符
如果服务器收到数据后发现是被转化过的, 那怎么识别呢? 其实被转义字符串与使用unicode_escape对字符串进行编码再使用utf-8进
行解码的结果一致, 代码如下:
In[129]:msgOut[129]:'中文'In[130]:msg.encode('unicode_escape').decode('utf-8')Out[130]:'\\u4e2d\\u6587'
所以识别只要反过来使用utf-8编码再使用unicode_escape解码就可以了.
转义是如何进行的
现在来看一下 json.dumps 到底是怎么对字符进行转义的. 在 json.dumps 源码中仔细调试的话会发现, 它调用的是
JSONEncoder.encode 方法, 而 encode 中的代码片段如下:
ifself.ensure_ascii:returnencode_basestring_ascii(o)else:returnencode_basestring(o)
它会根据 ensure_ascii 的值选择调用函数. 而 encode_basestring_ascii 的值是 (c_encode_basestring_ascii or
py_encode_basestring_ascii), 也就是默认是用 C 实现的版本, 其次使用 Python 实现的版本, 既然有 Python 版本, 当然要看一下是怎么
实现的, py_encode_basestring_ascii 可以直接使用 from json.encoder import py_encode_basestring_ascii 导入, 直接在其内部就可
以调试. 下面是其源码:
defpy_encode_basestring_ascii(s):"""ReturnanASCII-onlyJSONrepresentationofaPythonstring"""defreplace(match):s=match.group(0)try:returnESCAPE_DCT[s]exceptKeyError:n=ord(s)ifn<0x10000:return'\\u{0:04x}'.format(n)#return'\\u%04x'%(n,)else:#surrogatepairn-=0x10000s1=0xd800|((n>>10)&0x3ff)s2=0xdc00|(n&0x3ff)return'\\u{0:04x}\\u{1:04x}'.format(s1,s2)return'"'+ESCAPE_ASCII.sub(replace,s)+'"'
从最后的return可以看到它实际上是正则匹配替换然后在前后添加双引号.ESCAPE_ASCII的定义如下:
ESCAPE_ASCII=re.compile(r'([\\"]|[^\-~])')
其中 ([\\"] 用于匹配 \\ 和 ", 而 [^\ -~] 表示 \ -~ 取反(这里的反斜杠貌似是对空格进行转义, 我不是很理解, 不进行转义依旧可以匹配
到), 在 ASCII 表里, 空格字符 对应十进制是 40, ~ 是 176, 这是所有的 可打印字符, 取反就是所有编码不在 40 ~ 176 的字符, 所以中文
就会被匹配到, 下面为 ASCII表:
对于匹配到的字符, 会传入回调函数replace做转义.replace函数中的ESCAPE_DCT为:
ESCAPE_DCT={'\\':'\\\\','"':'\\"','\b':'\\b','\f':'\\f','\n':'\\n','\r':'\\r','\t':'\\t',}
先从 ESCAPE_DCT 中获取 制表符、换行符 等常用字符的转义, 如果失败就获取它的 Unicode 码点, 然后判断是否为小于 0x10000 即是
否为 两字节 字符(两字节最大为 0xFFFF ) , 如果是就格式化为 Unicode 码, 如果不是就使用 四字节 表示.
所以在使用requests时, 如果数据要使用json传输并且有中文, 那么需要手动将字典进行dump.
以上是在Python中使用JSON时的注意事项的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。