文章很不错。
不过挑几个 Python 2 相关的错,或者说是表述不准确的地方:
1--
「 python2 是把字符串直接经过 utf-8 编码保存的」
「出错的原因是因为 python2 当遇到 ASCII 表中没有的字符的时候,默认会把它们使用 utf-8 编码来存储」
其实并不是这样的,Python 2 并没有默认用 utf-8 编码来保存字符串,实际上在终端下输入的字符串的编码是由终端的编码决定的,也就是说终端用的是什么编码,Python 接收到的字符串就是什么编码。你这句话最多只能适用于 Mac 和大多数 Linux 系统等,因为它们终端的默认编码是 utf-8,所以在终端下输入 "你好",Python 接收到的是 utf-8 编码的 '\xe4\xbd\xa0\xe5\xa5\xbd'。但是在 Windows 下就不是这样了:
>>> '你好'
'\xc4\xe3\xba\xc3'
结果并不是 utf-8 编码的 '\xe4\xbd\xa0\xe5\xa5\xbd'。这是因为中文 Windows 系统 cmd 的默认编码是 cp936 (GBK),而不是 utf-8。可以通过 sys.stdin.encoding 来查看这个编码:
>>> import sys
>>> sys.stdin.encoding
'cp936'
所以在这种情况下输入 "你好",Python 接收到的二进制数据会是 '\xc4\xe3\xba\xc3',也就是 "你好" 的 cp936 编码:
>>> u'你好'.encode('cp936')
'\xc4\xe3\xba\xc3'
这个道理和 Python 读取文件内字符串是一样的,文件是什么编码,Python 读取到的字符串就是什么编码。Python 2 并没有默认使用 utf-8 编码来保存或读取字符串,而是由输入端(文件、终端等)的编码决定了 Python 2 获取到的字符串的编码。
2--
「
>>> '你好'.encode('gb2312')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
出错的原因是因为 python2 当遇到 ASCII 表中没有的字符的时候,默认会把它们使用 utf-8 编码来存储,但是 gbk 编码表无法对 utf-8 编码进行解码,这句话你可能需要读完文章才能理解。
」
这个例子里出错的原因并不是 gbk 编码表无法对 utf-8 编码进行解码。注意看这里报的错是 UnicodeDecodeError: 'ascii' codec can't decode,说的是 Python 无法用 ascii 编码来解码 '你好',和 gbk (gb2312) 编码没关系。而且 encode 是执行编码操作不是解码操作,你是在用 gb2312 对字符串进行编码,不是你文中说的「 gbk 编码表……进行解码」。
这里出错的真正原因是:你在 Python 2 下对 str 类型的字符串进行了 encode 操作,encode 操作只对 unicode 类型字符串才有意义( Python 3 里甚至不允许对非 unicode 字符串 (bytes) 进行 encode 操作,也不允许对 unicode 字符串 (str) 进行 decode 操作),所以 Python 2 在这里会隐式地尝试将 str 类型的 '你好' decode 为 unicode 类型字符串后再进行你要的 encode,这个隐式的 decode 操作使用的编码是 Python 2 的默认编码—— ascii,Python 的默认编码可以通过以下命令查看:
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
所以执行 '你好'.encode('gb2312') 时相当于执行了:
'你好'.decode('ascii').encode('gb2312')
ascii 无法解码中文,所以报了 UnicodeDecodeError 错,此时还没有真正执行 encode('gb2312'),所以例子和你的解释并不适用。
正确的例子应该是:
>>> '\xe4\xbd\xa0\xe5\xa5\xbd'.decode('gbk')
u'\u6d63\u72b2\u30bd'
>>> u'\u6d63\u72b2\u30bd' == u'你好'
False
不用 '你好'.decode('gbk') 来举例是因为正如上文 1 所说,这段代码在中文 Windows 系统下是没有编码错误的:
>>> '你好'.decode('gbk') == u'你好'
True
而 '\xe4\xbd\xa0\xe5\xa5\xbd'.decode('gbk') 这个例子你会发现 Python 2 是能顺利运行没有报错的,并不像你说的「 gbk 编码表无法对 utf-8 编码进行解码」那样出现 UnicodeDecodeError 错误。甚至这个结果是能直接 print 出来的:
>>> print('\xe4\xbd\xa0\xe5\xa5\xbd'.decode('gbk'))
浣犲ソ
这是因为 '你好' 的 utf-8 编码和 '浣犲ソ' 的 gbk 编码是重合的,都是 '\xe4\xbd\xa0\xe5\xa5\xbd':
>>> u'你好'.encode('utf-8')
'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> u'浣犲ソ'.encode('gbk')
'\xe4\xbd\xa0\xe5\xa5\xbd'
所以 gbk 编码表其实是可以对部分 utf-8 编码的字符串进行解码的,只是解码的结果不是我们想要的,也就是出现了乱码问题。
3--
「
另外常见编码错误就是使用错误的编码保存字符串,例如使用 ASCII 表保存”你好”,因为 ASCII 表里面没有对应的字符,它不知道如何保存。
>>> '你好'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
」
这个例子应该是在 Python 3 下执行的结果,'你好' 得是 unicode 类型字符串对其 encode('ascii') 才会报 UnicodeEncodeError 错误,否则在 Python 2 下应该如 2 的例子一样报的是 UnicodeDecodeError 错误:
>>> '你好'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
也就是相当于执行了:
'你好'.decode('ascii').encode('ascii')
正确的例子应该是:
>>> u'你好'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
--
我以前也写过篇和 Python 2 中文 Unicode 编码问题有关的文章,供你参考:
https://www.v2ex.com/t/163786