Python 理清编码问题 : 从 ASCII 到 GB 系列

共 3035字,需浏览 7分钟

 ·

2021-04-07 17:12

字符编码问题不仅在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无法编码简体汉字。仅仅中日韩三国和台湾地区,就有许多种编码方案,这些多字节编码方案相互交流,依然会产生乱码问题。
“大编码”要求,迫在眉睫。

总结

  • 从ASCII的简单,但不支持其他国家文字。
  • 到扩展ASCII支持其他国文字,但产生乱码。
  • 再到MBCS多字节编码可以支持汉语这种很多字符的语言,但由于太多语言编码导致各自为战互不兼容。

作者:巩庆奎,大奎,对计算机、电子信息工程感兴趣。gongqingkui at 126.com

赞 赏 作 者



更多阅读



2020 年最佳流行 Python 库 Top 10


2020 Python中文社区热门文章 Top 10


5分钟快速掌握 Python 定时任务框架

特别推荐




点击下方阅读原文加入社区会员

浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报