知行编程网知行编程网  2022-11-28 19:30 知行编程网 隐藏边栏  8 
文章评分 0 次,平均分 0.0
导语: 本文主要介绍了关于在 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]: 20013

In [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]: msg
Out[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 中的代码片段如下:

if self.ensure_ascii:
    return encode_basestring_ascii(o)
else:
    return encode_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 导入, 直接在其内部就可

以调试. 下面是其源码:

def py_encode_basestring_ascii(s):
    """Return an ASCII-only JSON representation of a Python string

    """
    def replace(match):
        s = match.group(0)
        try:
            return ESCAPE_DCT[s]
        except KeyError:
            n = ord(s)
            if n < 0x10000:
                return '\\u{0:04x}'.format(n)
                #return '\\u%04x' % (n,)
            else:
                # surrogate pair
                n -= 0x10000
                s1 = 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'([\\"]|[^\ -~])')

其中([\\"]是用来匹配\\和"的,而[^\ -~]表示\ -~是反的(这里的反斜杠好像是对空格进行转义,不是很懂,不是转换仍然可以匹配

to),在ASCII表中,空格符对应十进制的40,~是176,都是可打印的字符,倒过来就是编码不在40到176之间的所有字符,所以中文

就会被匹配到, 下面为 ASCII表:

在 Python 中使用 JSON 时要注意的编码问题

对于匹配到的字符,会传入回调函数replace进行转义。替换函数中的 ESCAPE_DCT 是:

ESCAPE_DCT = {
    '\\': '\\\\',
    '"': '\\"',
    '\b': '\\b',
    '\f': '\\f',
    '\n': '\\n',
    '\r': '\\r',
    '\t': '\\t',
}

先从ESCAPE_DCT中获取制表符、换行符等常用字符的转义,如果失败则获取其Unicode码位,然后判断是否小于0x10000

不是,是一个双字节的字符(双字节是 0xFFFF ),如果是,就格式化成Unicode码,如果不是,就用四个字节表示。

总结

所以在使用requests的时候,如果数据需要使用json传输,并且有中文,就需要手动dump字典。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

知行编程网
知行编程网 关注:1    粉丝:1
这个人很懒,什么都没写
扫一扫二维码分享