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

Python中文社区

共 3035字,需浏览 7分钟

 · 2021-04-07

字符编码问题不仅在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 定时任务框架

特别推荐




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

浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

举报