字符编码问题不仅在Python中,在信息处理的方方面面都能遇到。巴别塔之后,人类语言交流有了隔阂,更不要说抽象的文字了。捉摸不透的编码问题,成为编程路上的拦路虎,使遇到问题的程序员抓耳挠腮:穷尽所有编码、解码函数,依然不得其解;偶然成功解决了问题,也不知其所以然。
巴别塔 人类联合起来建设通往天堂的高塔,上帝为阻止人类的计划,让人类说不同的语言,使人类相互不能沟通,计划因此失败,人类至此各散东西。
Python2中,令人诟病的str类型,既充当字符串,又是字节序列。所幸Python3进行了改革:str仅用来表示字符串,用array来表示字符编码后的字节序列,同时Python采用UTF-8作默认编码。这大大简化了编码处理问题。但编程中我们仍常常遇到EncodeError和DecodeError问题,这是没有深入掌握编码问题的缘故。本文尝试回溯编码产生和发展的历史,力图在“编码问题毛线团”中整理出一条脉络,解决这个不定时炸弹。编码问题,根源在于人类语言的多样性,随着计算机技术的扩散,而逐渐发生和演化。让我们从“远古计算机”开始。英语——ASCII码
所谓编码(码表、字符编码)就是把一个语言中使用的字符、符号统一编号,组成一个表,每个字符都有一个数字对应。计算机内部并不处理具体字符,而是处理这个数字编号。计算机源于美国,英语只使用26个字母,算上大小写也才52个字符,数量很少,编码很容易。我们都知道计算机采用二进制运算和存储,一个8bit的字节(byte)可以表示2^8=256种情况,我们很容易把英语字母这些写数字建立一一对应的关系。这就是ASCII码。ASCII码不使用字节的最高位,只用了7bit空间,编号是00-7f,存储128个字符和控制符。Python中的ord函数,返回字符的码位。>>> print([ord(c) for c in 'abAB'])
[97, 98, 65, 66]
可见,字母a对应数字编号97,b对应数字编号98……以此类推。ASCII码完美解决了英语字母在计算机中的表示问题。其他字母语言——扩展ASCII码
其他字母语言,例如法语、俄语、西班牙语,使用的字符不同于英语,怎么办?好在ASCII码只用了七位128个,全部使用完整8位的话,还可以再扩展128个字符,对于大多数字母语言来说,这些空间够用了。每种字母语言的前128位和ASCII码一样,后128空间自由定义。但问题在于,每种语言各自扩展后,大于127的字符编码,在不同的语言里指代不同的字符。如编码130,在德语、土耳其语和俄语中分别指代三个完全不同的字符。但如下程序中,列表codes中的三个值,分别是上述三个语言的编码。b = bytes([0x82]) #0x82 == 130
codes = ['cp273','cp857','cp866']
print([b.decode(code) for code in codes])
输出['b', 'é', 'В'],也就是说127之后的内容,大家各不相同,产生乱码:一份俄语编码写就的文档,在土耳其的电脑里,完全被土耳其编码格式解码成土耳其字符组成的无意义字符串。彻底解决乱码只能靠一个包含所有语言字符的“大编码”。虽然字母语言有乱码问题,但已通过扩展ASCII码解决了有无的问题。如果只在本国用,或者干脆传文件的时候附加具体编码名,倒也勉强可行。仅用一个字节就可以解决字母语言的问题,对于需要大量字符的汉字、日语和繁体字等,就比较麻烦了。汉字——GB系列编码
勤劳勇敢的中国人民使用的汉字,总数恐怕不下十万,常用字也是万级别。一个字节256个存储空间,显然不够看。解决这一问题,最简单粗暴的方法就是增加表示空间,一个字节表示ASCII码;两个表示汉字;两个不够,用三个,三个不够,用四个……这个方案保持了ASCII兼容性,又能借助多字节空间编码数以万计的汉字。- GB2312 国标,双字节表示,1981年,6763个汉字
- GBK 国标扩展,双字节,扩充、兼容GB2312,1995年,21003汉字
- GB18030 GBK扩充,单、双、四字节,27484字,兼容GB2312,GBK
- BIG5 台湾地区繁体中文字符集,双字节,1984年,13053汉字
因用多个字节表示,以上统称多字节编码MBCS,作为特例,双字节编码称DBCS。我们来看GB2312、GBK、GB18030如何编码汉字。Python中encode函数根据给定编码,将 字符 编码成 字节序列 。c='巩'
codes=['gb2312','gbk','gb18030']
print([c.encode(code) for code in codes])
输出结果:[b'\xb9\xae', b'\xb9\xae', b'\xb9\xae']
。可见,汉字巩在这三个编码中,都被编码为双字节0xb9ae了,这是因为GB系列是兼容的。至此,汉字的编码和扩展问题得到解决,我们常用的GB系列可以方便编码汉字。但问题依然存在,当我们和台湾地区交流时,又发生了新问题:>>> c.encode('big5')
Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'big5' codec can not encode character '\u5de9' in position 0: illegal multibyte sequence
BIG5无法编码简体汉字。仅仅中日韩三国和台湾地区,就有许多种编码方案,这些多字节编码方案相互交流,依然会产生乱码问题。总结
- 再到MBCS多字节编码可以支持汉语这种很多字符的语言,但由于太多语言编码导致各自为战互不兼容。