Python中文处理
摘要
在Python(Python2.x)中使用中文字符常常给我们带来困扰。经常会遇到以下的错误:
1 | UnicodeDecodeError: ’ascii’ codec can’t decode byte 0xe9 in position 1: ordinal not in range(128) |
这是由于中文的字符编解码错误造成的。下面就让我们来研究一下字符编码和Python 的字符处理机制。注:本文Python 的内容仅适用于2.x,如果你使用3.x,那么恭喜你,你将不存在这些问题。
借用《Dive into Python3》中的一句话:
“你所了解的关于字符串的知识都是错的。”
基础知识
当人们说起“文本”,他们通常指显示在屏幕上的字符或者它的记号;但是计算机不能直接处理这些字符和标记;它们只认识位(bit) 和字节(byte)。实际上,从屏幕上的每一块文本都是以某种字符编码(character encoding) 的方式保存的。粗略地说就是,字符编码提供一种映射,使屏幕上显示的内容和内存、磁盘内存储的内容对应起来。有许多种不同的字符编码,有一些是为特定的语言,比如俄语、中文或者英语,设计、优化的,另外一些则可以用于多种语言的编码。
在实际操作中则会比上边描述的更复杂一些。许多字符在几种编码里是共用的,但是在实际的内存或者磁盘上,不同的编码方式可能会使用不同的字节序列来存储他们。所以,你可以把字符编码当做一种解码密钥。当有人给你一个字节序列—文件,网页,或者别的什么—并且告诉你它们是“文本”时,就需要知道他们使用了何种编码方式,然后才能将这些字节序列解码成字符。如果他们给的是错误的“密钥”或者根本没有给你“密钥”,那就得自己来破解这段编码,这可是一个艰难的任务。有可能你使用了错误的解码方式,然出现一些莫名其妙的结果。
现有的字符编码各类给世界上每种主要的语言都提供了编码方案。由于每种语言的各不相同,而且在以前内存和硬盘都很昂贵,所以每种字符编码都为特定的语言做了优化,每种编码都使用数字(0–255) 来代表这种语言的字符。比如,你熟悉ascii 编码,它将英语中的字符都当做从0–127 的数字来存储。西欧的一些语言,比如法语,西班牙语和德语等,比英语有更多的字母。这些语言最常用的编码方式是CP-1252,CP-1252和ascii 在0–127 这个范围内的字符是一样的,而比ascii 多的字符扩展到了128–255 这个范围。然而,它仍然是一种单字节的编码方式;可能的最大数字为255,这仍然可以用一个字节来表示。然而,像中文,日语和韩语等语言,他们的字符如此之多而不得不需要多字节编码的字符集。
字符编码
ASCII
ASCII(American Standard Code for Information Interchange),是一种单字节的编码。计算机世界里一开始只有英文,而单字节可以表示256 个不同的字符,可以表示所有的英文字符和许多的控制符号。ASCII码一共规定了128 个字符的编码,比如空格”SPACE” 是32(二进制00100000
),大写的字母A 是65(二进制01000001
)。这128 个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7 位,最前面的1 位统一规定为0。
MBCS
英语用128 个符号编码就够了,但是用来表示其他语言,单字节的ASCII 已无法满足需求。后来每个语言就制定了一套自己的编码,由于单字节能表示的字符太少,而且同时也需要与ASCII 编码保持兼容,所以这些编码纷纷使用了多字节来表示字符。他们的规则是,如果第一个字节是\x80
以下,则仍然表示ASCII字符;而如果是\x80
以上,则跟下一个字节(或几个字节)一起表示一个字符。
这里,IBM 发明了一个叫Code Page 的概念,将这些编码都收入囊中并分配页码,GBK 是第936 页,也就是CP936。所以,也可以使用CP936 表示GBK。MBCS(Multi-Byte Character Set) 是这些编码的统称。目前为止大家都是用了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。
Unicode
正如上面所说,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。于是大家坐在一起拍脑袋想出来一个方法:所有语言的字符都用同一种字符集来表示,这就是Unicode。
Unicode 当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639
表示阿拉伯字母Ain,U+0041
表示英语的大写字母A,U+548C
表示汉字“和”。具体的符对应表,可以查询unicode.org,或者专门的汉字对应表。
最初的Unicode 标准UCS-2 使用两个字节表示一个字符,但过了不久有人觉得256*256 太少了,还是不够用,于是出现了UCS-4 标准,它使用4 个字节表示一个字符,不过我们用的最多的仍然是UCS-2。需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码(比如“和”这个字的码位是548C
),却没有规定这个二进制代码应该如何存储。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责。
一开始这事很简单,直接使用UCS 的码位来保存,这就是UTF-16 或和UTF-32,比如,“和”直接使用\x54x8C
保存(UTF-16-BE),或是倒过来使用\x8C\x54
保存(UTF-16-LE)。
UTF-8
上面说使用UTF-16要用两个字节来存储一个字符。但用着用着美国人觉得自己吃了大亏,以前英文字母只需要一个字节就能保存了,现在大锅饭一吃变成了两个字节,这对于存储来说是极大的浪费,文本文件的大小会因此大出一倍,这是无法接受的⋯⋯于是UTF-8横空出世。
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8 的编码规则很简单,只有二条:
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII 码是相同的。
- 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1 位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode 码。
下表总结了编码规则,字母x 表示可用编码的位。
Unicode 符号范围(十六进制) | UTF-8 编码方式(二进制) |
---|---|
0000 0000 – 0000 007F | 0xxxxxxx |
0000 0080 – 0000 07FF | 110xxxxx 10xxxxxx |
0000 0800 – 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000 – 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
根据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。下面,还是以汉字“和”为例,演示如何实现UTF-8编码。已知“和”的unicode是548C
(101010010001100
),根据上表,可以发现548C
处在第三行的范围内(0000 0800 – 0000 FFFF
),因此“和”的UTF-8编码需要三个字节,即格式是”1110xxxx 10xxxxxx 10xxxxxx
“。然后,从“和”的最后一个二进制位开始,依次从后向前填入格式中的x
,多出的位补0。这样就得到了,“和”的UTF-8编码是”11100101 10010010 10001100
“,转换成十六进制就是E5928C
。
缺点:因为每个字符使用不同数量的字节编码,所以寻找串中第N个字符是一个O(N)复杂度的操作—即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。
优点:在处理经常会用到的ascii字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。同时,由位操作的天性使然,使用UTF-8不再存在字节顺序的问题了。一份以utf-8编码的文档在不同的计算机之间是一样的比特流。
Python 的中文编码
字符编码声明
源代码文件中,如果有用到非ASCII字符,则需要在文件头部进行字符编码的声明,如下:
1 | #-*- coding:encoding -*- |
根据这个声明,Python会尝试将文件中的字符编码转为encoding编码,并且,它尽可能的将指定地编码直接写成Unicode文本。注意,coding:encoding
只是告诉Python文件使用了encoding格式的编码,但是编辑器可能会以自己的方式存储.py
文件,因此最后文件保存的时候还需要编码中选指定的ecoding才行。
str和unicode
Python 有两种字符串类型,str与unicode。str和unicode都是basestring的子类。严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列(根据不同的编码规则其编码长度不一样)。unicode才是真正意义上的字符串,对字节串str使用正确的字符编码进行解码后获得(其长度和“显示”的长度一致)。在Python3已经取消了str,让所有的字符串都是unicode 。
1 | # -*- coding:utf-8 -*- |
中文字符编解码
Python中unicode才是字符的唯一内码,而大家常用的字符集如gb2312,gbk,utf-8,以及ascii都是字符的二进制(字节)编码形式。把字符从unicode 转换成二进制编码,当然是要encode(编码)。反过来,在Python中出现的str都是用字符集编码的ansi字符串。Python本身并不知道str的编码,需要由开发者指定正确的字符集decode。
1 | # -*- coding: utf-8 -*- |
str的编码是根据系统的默认编码来的。简体中文Windows的默认编码是GBK,在我的机器上(Archlinux)的默认编码是UTF-8。因此,s.decode(‘utf-8’)能成功解码,而s.decode(‘gb2312’) 就不能解码。任何编码decode都会得到unicode,且unicode可以encode成任何编码。
因此,处理中文数据时最好采用如下方式:
- Decode early(尽早decode,将文件中的内容转化成unicode再进行下一步处理)
- Unicode everywhere(程序内部处理都用unicode)
- Encode late(最后encode回所需的encoding, 例如把最终结果写进结果文件)