人类使用文本,计算机使用字节序列。
——Esther Nam 和 Travis Fischer
“Character Encoding and Unicode in Python”
前奏:编码与解码
编码和解码的概念:把码位转换成字节序列的过程是编码;把字节序列转换成码位的过程是解码。
-
>>> str_test = "今天天气好蜣螂,处处好风光。"
-
>>> rst = str_test.encode('utf8') # 编码过程,让计算机明白
-
>>> rst
-
b'\xe4\xbb\x8a\xe5\xa4\xa9\xe5\xa4\xa9\xe6\xb0\x94\xe5\xa5\xbd\xe8\x9c\xa3\xe8\x9e\x82\xef\xbc\x8c\xe5\xa4\x84\xe5\xa4\x84\xe5\xa5\xbd\xe9\xa3\x8e\xe5\x85\x89\xe3\x80\x82'
-
>>> rst.decode('utf8') # 解码过程,让人类明白
-
'今天天气好蜣螂,处处好风光。'
天启:黎明之前
标准:字符集
双方要想实现通信,需要先商量一下,比如两个同学考试前商量着传答案,可以约定,伸一个手指头代表选择A,眨眨眼代表选择B….
有人可能会问,为啥不是伸两个指头代表选择B?这也是会出现编码问题的一个重要原因,约定不同!(考试之前有一套行之有效的统一的约定是多么的至关重要)。
同样,回到计算机的编码问题上,人给计算机传答案,得和计算机事先约定好一套行之有效的《逢考必过宝典》。常用的宝典及其发展历史大致如下。
ASCII(erican Standard Code for Information Interchange)编码,不厚道的美国人
用7bit进行编码,因为美国使用英文,字符比较少,26个英文字母,算上大小写才52个,加上其他的一些标点和控制键,128种状态足够了,压根没打算和中日韩这些学生传答案。
iso8859_1: 拉丁语系国家
拉丁语系国家,美国大哥仗着自己英语好,不打算带着自己玩了,那好吧,自己也建个群,搞了一套《阿拉伯逢考必过手册》。拉丁语系国家感觉自己也飘起来了,比老美也好不到哪里,用了8bit编码。最多也就256种状态。也就是最多存储256个符号。而汉字少数也得几万,常用的也得几千吧,根本无法满足中国同学作文与阅读理解考试时候的刚需!
GBK和GB2312:汉字编码
于是,聪明的中国同学也开始开发自己的《中华助考神奇,三年模拟五年实战》。中国国家比较大,拥有56个民族,为了方便各民族,GB代表是国家标准的意思——国标。与此同时邻座的日韩也搞了自己的《宝典》。
GBK与GB2312的区别:https://zhidao.baidu.com/question/40269499.html
Unicode(utf-8): 万国码
考试座位通常是随机分配的,比如考数学的时美美同学与数学比较好的中中同学邻座,可是这两位同学练得分别是两门相克的武林绝学,根本没办法统一起来。于是经过几场考试后,大家为了应对残酷无情的监考老师,决定开发一套通用的《地球考试手册》就是Unicode字符集(编码)。这下可算让大家松了一口老气!
矛盾:各为其主
第二天考场上,韩韩同学(韩国)和美美同学(美国)在邻座,两个人商量好要一块把《马
克思原理》拿到全班最高分,美美遇到了一个不确定的答案,自己给韩韩同学发信号,但是他感觉《地球考试手册》太复杂,没有自己的《宝典》方便。于是就用自己的《宝典》向韩韩求助,可想而知,韩韩同学看不明白,转而用自己的宝典和自己的同班同学去交流。留下美美一个人饮酒醉。
中中与俄俄同学就比较聪明,他们一样用各自熟系的《宝典》,但是带了个长江七号点读机,长江七号先把《中华助考神奇,三年模拟五年实战》转换成《地球考试手册》然后再转换
《俄俄宝典三千问》。同样,当俄俄同学给中中同学发信号时,这个过程逆过来。最后结果可想而知,中中同学和俄俄同学取得了不错的成绩。
战场:少年小P(python)的成长烦恼
同样的道理,当python要做编码转换的时候,会借助于内部的编码,转换过程是这样的:
原有编码 -> 内部编码 -> 目的编码
Python2默认的是ASCII编码、需要通过Unicode编码实现不同编码的额转换
注意:unicode和utf-8之间不需要转换,可以直接互相打印,GBK如果需要和iso8859_1编码要通unicode编码转换。
UTF-8编码:是对Unicode编码的压缩和优化,他不再使用最少使用2个字节,而是将所有的字符和符号进行分类:ascii码中的内容用1个字节保存、欧洲的字符用2个字节保存,东亚的字符用3个字节保存...
每次使用py2进行编码的时候,要在文件头添加# encoding:utf-8,非常烦恼。
高潮:小P的不惑之年——py3
Python 3 明确区分了人类可读的文本字符串和原始的字节序列。隐式地把字节序列转换成 Unicode 文本已成过去。
“字符串”是个相当简单的概念:一个字符串是一个字符序列。问题出在“字符”的定义上。在 2015 年,“字符”的最佳定义是 Unicode 字符。因此,从 Python 3 的str 对象中获取的元素是 Unicode 字符,这相当于从 Python 2 的unicode 对象中获取的元素,而不是从 Python 2 的 str 对象中获取的原始字节序列。
现在,Python 3 的源码不再限于使用 ASCII,而是默认使用优秀的 UTF-8 编码,因此要修正源码的陈旧编码(如 'cp1252')问题,最好将其转换成 UTF-8,别去麻烦 coding 注释。如果你用的编辑器不支持 UTF-8,那么是时候换一个了
落幕:英雄的无奈
图:编码问题及解决方案
多数非 UTF 编解码器只能处理 Unicode 字符的一小部分子集。把文本转换成字节序列时,如果目标编码中没有定义某个字符,那就会抛出UnicodeEncodeError 异常,除非把 errors 参数传给编码方法或函数,对错误进行特殊处理。
不是每一个字节都包含有效的 ASCII 字符,也不是每一个字符序列都是有效的 UTF-8 或 UTF-16。因此,把二进制序列转换成文本时,如果假设是这两个编码中的一个,遇到无法转换的字节序列时会抛出UnicodeDecodeError。
使用预期之外的编码加载模块时抛出的SyntaxError
Python 3 默认使用 UTF-8 编码源码,Python 2(从 2.5 开始)则默认使用ASCII。如果加载的 .py 模块中包含 UTF-8 之外的数据,而且没有声明编码,会得到类似下面的消息:
SyntaxError: Non-UTF-8 code starting with '\xe1' in file ola.py on line1, but no encoding declared; see http://python.org/dev/peps/pep-0263/for details
图展示了不同编解码器对“A”和高音谱号等字符编码后得到的字节序列。注意,后 3 种是可变长度的多字节编码。
图: 12个字符,它们的码位及不同编码的字节表述(十六进制,星号表明该编码不支持表示该字符)
图中的星号表明,某些编码(如 ASCII 和多字节的 GB2312)不能表示所有 Unicode 字符。然而,UTF 编码的设计目的就是处理每一个Unicode 码位。
图中展示的是一些典型编码,介绍如下。
latin1(即 iso8859_1)
一种重要的编码,是其他编码的基础,例如 cp1252 和Unicode(注意,latin1 与 cp1252 的字节值是一样的,甚至连码位也相同)。
cp1252
Microsoft 制定的 latin1 超集,添加了有用的符号,例如弯引号和€(欧元);有些 Windows 应用把它称为“ANSI”,但它并不是 ANSI 标准。
cp437
IBM PC 最初的字符集,包含框图符号。与后来出现的 latin1 不兼容。
gb2312
用于编码简体中文的陈旧标准;这是亚洲语言中使用较广泛的多字节编码之一。
utf-8
目前 Web 中最常见的 8 位编码; 与 ASCII 兼容(纯 ASCII 文本是有效的 UTF-8 文本)。
小端存储、大端存储https://www.cnblogs.com/wood2012/p/8540169.html
在小字节序设备中,各个码位的最低有效字节在前面。
在大字节序 CPU 中,编码顺序是相反的
UTF-16 有两个变种:UTF-16LE,显式指明使用小字节序;UTF-16BE,显式指明使用大字节序。如果使用这两个变种,不会生成 BOM(一种在utf_16的隐式标志、UTF-16 编码的序列开头有几个额外的字节)
- >>> u16le = 'El Niño'.encode('utf_16le')
-
>>> list(u16le)
-
[69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0]
-
>>> u16be = 'El Niño'.encode('utf_16be')
-
>>> list(u16be)
-
[0, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111]
如果有 BOM,UTF-16 编解码器会将其过滤掉,为你提供没有前导ZERO WIDTH NO-BREAK SPACE 字符的真正文本。根据标准,如果文件使用 UTF-16 编码,而且没有 BOM,那么应该假定它使用的是 UTF-16BE(大字节序)编码。然而,Intel x86 架构用的是小字节序,因此有很多文件用的是不带 BOM 的小字节序 UTF-16 编码。
与字节序有关的问题只对一个字(word)占多个字节的编码(如 UTF-16 和 UTF-32)有影响。UTF-8 的一大优势是,不管设备使用哪种字节序,生成的字节序列始终一致,因此不需要 BOM。尽管如此,某些Windows 应用(尤其是 Notepad)依然会在 UTF-8 编码的文件中添加BOM;而且,Excel 会根据有没有 BOM 确定文件是不是 UTF-8 编码,否则,它假设内容使用 Windows 代码页(codepage)编码。UTF-8 编码的 U+FEFF 字符是一个三字节序列:b'\xef\xbb\xbf'。因此,如果文
件以这三个字节开头,有可能是带有 BOM 的 UTF-8 文件。然而,Python 不会因为文件以 b'\xef\xbb\xbf' 开头就自动假定它是 UTF-8编码的。
参考资料:《流畅的Python》 Luciano Ramalho 人民邮电出版社 安道、吴珂译