@wzhang1117
2014-05-16T22:53:18.000000Z
字数 3359
阅读 3676
python
编码
unicode
python程序在处理中文文本时经常遇到编码方面的错误或异常。本文首先讲解了与编码有关的最重要的两个函数,然后针对实际应用中的常见问题进行了分析。
一般用于对unicode字符串进行编码。
为什么需要对unicode进行编码?
unicode码可以理解为对所有通用字符进行了编号,每个字符对应一个数字代号现在大多数使用的UCS-2使用两个字节表示一个字符,例如“中”的unicode码为0x4E2D。
>>> unicodeStr = u'中文123'
>>> unicodeStr
u'\u4e2d\u6587123'
但是怎么存储和传输unicode码呢?
一种最简单的办法是直接使用unicode码,这就是utf16。但是这种方法要考虑两个问题,一是大小端的问题,前面的FFFE
(BOM)就是表明小端存储;而是消耗资源,对于ascii码字符浪费太大,对于只使用ascii码的用户来说浪费了一半资源(传输、存储)。针对这两个问题出现了utf8,utf8也可以有BOM,固定为EF BB BF
,但是官方不推荐使用,主要是微软用;针对第二个问题utf8使用的是不定长编码,对于ascii码,直接使用一个字节,对于其他字符,可以是两个三个甚至4字节。
#使用utf16进行编码,所有字符都使用两个字节表示
>>> unicodeStr.encode('utf16').encode('hex')
'fffe2d4e8765310032003300'
#使用utf8进行编码,汉字使用三字节,ascii使用一个字节表示
>>> unicodeStr.encode('utf8').encode('hex')
'e4b8ade69687313233'
unicode还可以编码成为gbk,gb2312格式。
#使用gbk,gb2312编码,汉字使用两字节,ascii一个字节
>>> unicodeStr.encode('gbk').encode('hex')
'd6d0cec4313233'
>>> unicodeStr.encode('gb2312').encode('hex')
'd6d0cec4313233'
encode还有其他作用,例如将一个字节串转换为hex序列,如上的encode('hex')
,还有将一个字节序列进行base64编码,如encode('base64')
,等等。
总之,对unicode进行编码的主要作用是生成一段可以进行存储和传输的字节序列或字节串。
和encode对应,主要用于将一个字节串按照指定方式解码成为unicode字符串,示例代码:
>>> utf8Str = '中文123'
#可以看出默认python使用utf8编码
>>> utf8Str.encode('hex')
'e4b8ade69687313233'
#如果使用其他方式解码都会出现错误
>>> utf8Str.decode('utf8')
u'\u4e2d\u6587123'
总之,对字节串进行解码的主要作用是生成unicode字符串。
>>> f = open("a.txt","w")
>>> f.write(unicodeStr)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
write方法可以理解为将一个字节串逐个写入到文件,而unicode并不是一个合法的字节串,而write默认将其当做ascii码处理,所以出现了异常。正确的做法是先将unicode编码为可传输的字节串,然后再写入。
#"中 %s"默认为utf8字节串
>>> "中 %s" % unicodeStr
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
出现异常往往是这种错误所致,这主要需要理解unicode字符串和字节串是两种不同类型的对象。正确的做法应该是:
#下面这样可以
>>> u"中 %s" % unicodeStr
u'\u4e2d \u4e2d\u6587123'
#这种情况没有问题
>>> u'test' == 'test'.encode('utf8')
True
>>> utf8Str = "中文123"
>>> gbkStr = u"中".encode('gbk')
>>> gbkStr in utf8Str
False
这种情况一般不会产生异常,但是程序结果却是错误的,因为程序并不知道这两种字符串的编码,它只是逐字节进行比较而已,类似的还有正则表达式等。正确的使用方式:
>>> utf8Str = "中文123"
>>> gbkStr = u"中".encode('gbk')
>>> gbkStr.decode('gbk') in utf8Str.decode('utf8')
True
#或者
>>> gbkStr.decode('gbk').encode('utf8') in utf8Str
True
至于选择何种方式,建议最好在程序内部只处理unicode,即先将所有需要处理的文本都转换成为unicode字符串,然后再进行处理,因为unicode是将中文字符当做单个字符处理的,而其它都是以字节串的形式处理的,在某些情况下可能出错,如计算长度,字符串分割等。
读文件的时候如果没有指定编码,则逐个字节读取,因此得到的是一个字节串。如果不知道这个字节串是以什么方法进行编码的,就直接使用,会出现上面的说的几个问题。所以python程序需要处理用户文本文件时一定要考虑文件的编码方式,当然你可以和用户约定好使用固定的编码方式(这种方式最可靠),但是如果针对普通用户,很难指望他知道什么是utf8,什么是gbk,gb2312等等。
因此大多数情况下需要进行判断编码类型。如果文件包含BOM信息,可以根据BOM的值(即读取的字节串的前三个字节)判断,如果不包含BOM,就只能根据字节串的编码特点进行识别了,但是这个过程比较复杂。有一个简便的方法是使用一个叫chardet
的模块,代码如下:
>>> chardet.detect(utf16Str)
{'confidence': 1.0, 'encoding': 'UTF-16LE'}
>>> chardet.detect(utf8Str)
{'confidence': 0.7525, 'encoding': 'utf-8'}
#识别错误的例子
>>> chardet.detect(u'中国'.encode('gbk'))
{'confidence': 0.7679697235616183, 'encoding': 'IBM855'}
但是chardet
模块有一个问题就是识别度,尤其是字符串比较短的时候经常识别错误,但是含有BOM或者稍微长一些的文本识别率还是非常高的,在几乎大多数实际应用中都应该没有问题。
要想彻底解决python中的编码问题,根本是要理解unicode字符串和已编码的字节串之间的区别。关于python中的编码问题,总结了以下几条原则:
- python源代码使用utf8存储
- python源文件头部使用coding声明
- 声明包含中文字符串的时候加前缀u
- 在程序内部只处理unicode字符串,所有包含特殊字符或中文字符的文本必须先decode成为unicode字符串
- 如果需要将字符串进行存储或传输,一定要使用encode编码成为字节串的形式,最好使用utf8的形式(通用),或者gbk(在存储汉字的时候比utf8节省存储空间)
- 如果需要处理输入文件,而且没有约定好编码方式,一定要使用程序检测编码方式