不论你是有着多年经验的 Python 老司机还是刚入门 Python 不久,你一定遇到过UnicodeEncodeError、UnicodeDecodeError 错误,每当遇到错误我们就拿着 encode、decode 函数翻来覆去的转换,有时试着试着问题就解决了,有时候怎么试都没辙,只有借用 Google 大神帮忙,但似乎很少去关心问题的本质是什么,下次遇到类似的问题重蹈覆辙,那么你有没有想过一次性彻底把 Python 字符编码给搞懂呢?
完全理解字符编码 与 Python 的渊源前,我们有必要把一些基础概念弄清楚,虽然有些概念我们每天都在接触甚至在使用它,但并不一定真正理解它。比如:字节、字符、字符集、字符码、字符编码。
字节字节(Byte)是计算机中数据存储的基本单元,一字节等于一个8位的比特,计算机中的所有数据,不论是保存在磁盘文件上的还是网络上传输的数据(文字、图片、视频、音频文件)都是由字节组成的。
字符你正在阅读的这篇文章就是由很多个字符(Character)构成的,字符一个信息单位,它是各种文字和符号的统称,比如一个英文字母是一个字符,一个汉字是一个字符,一个标点符号也是一个字符。
字符集字符集(Character Set)就是某个范围内字符的集合,不同的字符集规定了字符的个数,比如 ASCII 字符集总共有128个字符,包含了英文字母、阿拉伯数字、标点符号和控制符。而 GB2312 字符集定义了7445个字符,包含了绝大部分汉字字符。
字符码字符码(Code Point)指的是字符集中每个字符的数字编号,例如 ASCII 字符集用 0-127 连续的128个数字分别表示128个字符,例如 "A" 的字符码编号就是65。
字符编码字符编码(Character Encoding)是将字符集中的字符码映射为字节流的一种具体实现方案,常见的字符编码有 ASCII 编码、UTF-8 编码、GBK 编码等。某种意义上来说,字符集与字符编码有种对应关系,例如 ASCII 字符集对应 有 ASCII 编码。ASCII 字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如"A" 的编号是65,用单字节表示就是0×41,因此写入存储设备的时候就是b'01000001'。
编码、解码编码的过程是将字符转换成字节流,解码的过程是将字节流解析为字符。
理解了这些基本的术语概念后,我们就可以开始讨论计算机的字符编码的演进过程了。
从 ASCII 码说起说到字符编码,要从计算机的诞生开始讲起,计算机发明于美国,在英语世界里,常用字符非常有限,26个字母(大小写)、10个数字、标点符号、控制符,这些字符在计算机中用一个字节的存储空间来表示绰绰有余,因为一个字节相当于8个比特位,8个比特位可以表示256个符号。于是美国国家标准协会ANSI制定了一套字符编码的标准叫 ASCII(American Standard Code for Information Interchange),每个字符都对应唯一的一个数字,比如字符 "A" 对应数字是65,"B" 对应 66,以此类推。最早 ASCII 只定义了128个字符编码,包括96个文字和32个控制符号,一共128个字符只需要一个字节的7位就能表示所有的字符,因此 ASCII 只使用了一个字节的后7位,剩下最高位1比特被用作一些通讯系统的奇偶校验。下图就是 ASCII 码字符编码的十进制、二进制和字符的对应关系表
扩展的 ASCII:EASCII(ISO/8859-1)然而计算机慢慢地普及到其他西欧地区时,发现还有很多西欧字符是 ASCII 字符集中没有的,显然 ASCII 已经没法满足人们的需求了,好在 ASCII 字符只用了字节的7位 0×00~0x7F 共128个字符,于是他们在 ASCII 的基础上把原来的7位扩充到8位,把0×80-0xFF这后面的128个数字利用起来,叫 EASCII ,它完全兼容ASCII,扩展出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号。然而 EASCII 时代是一个混乱的时代,各个厂家都有自己的想法,大家没有统一标准,他们各自把最高位按照自己的标准实现了自己的一套字符编码标准,比较著名的就有 CP437, CP437 是 始祖IBM PC、MS-DOS使用的字符编码,如下图:
众多的 ASCII 扩充字符集之间互不兼容,这样导致人们无法正常交流,例如200在CP437字符集表示的字符是 È ,在 ISO/8859-1 字符集里面显示的就是 ╚,于是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准ISO/8859-1(Latin-1),它继承了 CP437 字符编码的128-159之间的字符,所以它是从160开始定义的,ISO-8859-1在 CP437 的基础上重新定义了 160~255之间的字符。