前言

对于曾经的 Python2 来说,编码处理一直是一门学问,但是切换到 Python3 以后错误编码就主要来自输入(包括从网页等数据源获取的输入)时的错误了。

Python3 编码基础

对于非英文字符,主流的表示方式有两种,一种是 Unicode 编码,另一种则是多字节编码(比如 GBK, SHIFT-JIS 等等),后者在不同的操作系统乃至同一个操作系统不同国家的版本都有所差异,这也造成了很多问题。但是为了保证兼容性,至今后者仍然被广泛使用。

在 Python3 中,str 也就是字符串类型的本质是一组 unicode ,而经过编码之后的则是一组字节码(bytes)。

注:以 >>> 开头的程序行代表输入,其余行代表输出。

>>> s = '天气'  # unicode
>>> b = s.encode('gbk')  # 编码而成的 gbk 字节码

>>> s
'天气'

>>> b  # 输出以b开头,代表是字节码
b'\xcc\xec\xc6\xf8'

输入多字节编码等时的正确输入姿势

当输入非英文字符时,需要注意输入的方式。
一个良好的设计是在输入时将字节码正确解码为 unicode ,然后程序处理中完全使用 unicode,在输出到文件等时再将 unicode 编码为所需的格式。
如果不显式的编码, Python 会自动以系统默认的编码格式进行编码,可以通过 sys.getdefaultencoding() 活动

注:以 >>> 开头的程序行代表输入,其余行代表输出。

>>> '天气'  # 直接输入
'天气'

>>> '\u5929\u6c14'  # 输入转义的 unicode 编码
'天气'

>>> # 输入 gbk 编码的字节码并解码
>>> b'\xcc\xec\xc6\xf8'.decode('gbk')
'天气'

>>> # 输入 SHIFT-JIS 编码的字节码并解码
>>> b'\x93V\x8bC'.decode('shift-jis')
'天気'

处理已经读入的错误编码

上面说的是理想的情况,但是有时当我们接手一些项目或者使用了错误保存的输入源时,即使我们正确地进行了输入,也会读入一些错误的编码,比如:

处理错误读入的 unicode 编码

当错误读入的内容是转义后的 Unicode 编码时,只需要简单的使用 bar.encode().decode('unicode-escape') 即可。

例程:

>>> # 正确输入的字符
>>> foo = '\u5929\u6c14'
>>> # r代表不进行转义
>>> bar = r'\u5929\u6c14'

>>> foo
'天气'

>>> bar
'\\u5929\\u6c14'

>>> # 以下语句代表将每个字符用中括号包起来之后输出
>>> for c in foo: print('[%s] ' % c, end='')
[] []

>>> for c in bar: print('[%s] ' % c, end='')
[\] [u] [5] [9] [2] [9] [\] [u] [6] [c] [1] [4]

>>> bar.encode().decode('unicode-escape')
'天气'

处理错误读入的多字节编码

错误读入的内容是转义后的多字节编码时,需要引入 Python 的标准库 codecs 处理,方法是 codecs.escape_decode(foo.encode())

例程:

>>> import codecs
>>> '天气'.encode('gbk')
b'\xcc\xec\xc6\xf8'

>>> foo = r'\xcc\xec\xc6\xf8'
>>> foo
'\\xcc\\xec\\xc6\\xf8'

>>> bar = codecs.escape_decode(foo.encode())
(b'\xcc\xec\xc6\xf8', 16)

>>> # 此时 bar[0] 的值就是正确的字节码
>>> # 已知字节码格式只需解码即可
>>> bar[0].decode('gbk')
'天气'

处理未知编码格式的字节码

如果不知道字节码的编码格式,可以使用第三方库 chardet 来解决。

chardet.

# 使用 pip 安装 chardet 库
pip3 install chardet

Python 例程:

>>> import chardet, codecs
>>> '真是好天气'.encode('gbk')
b'\xd5\xe6\xca\xc7\xba\xc3\xcc\xec\xc6\xf8'

>>> foo = r'\xd5\xe6\xca\xc7\xba\xc3\xcc\xec\xc6\xf8'
>>> bar = codecs.escape_decode(foo.encode())
>>> chardet.detect(bar[0])
{'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'}

>>> bar[0].decode(chardet.detect(bar[0]).get('encoding'))
'真是好天气'