0. 写在前面
起因:之前写个数据预处理程序的时候遇到了点问题,用re模块的正则查找方法search时总是找不出来(找错了或者出乱码),于是捣鼓捣鼓。
经过:查资料,做实验,发现用utf8编码的str类型的字符串在search方法中行不通,因为str是字节串,和字符之间没有固定的一一对应的关系,正则没法用字节串来进行正确匹配。
结果:把正则式和目标字符串都使用unicode类型,unicode和字符之间是两个字节对应一个字符的关系,正则可以根据这个来对字符进行匹配。
后续:突然觉得应该总结一下编码问题,防止再次入坑。于是有了此文。
1. ascii, unicode, utf8
ascii码:最早的编码,只有127个字符,包含英文字母,数字,标点符号和一些其它符号。一个字节表示一个字符。
unicode(统一码):一个字节不够放,全世界有各种语言的字符需要编码,于是unicode给所有的字符都设定了唯一编码。通常都是用两个字节表示一个字符(有些生僻的字要用四个字节)。所以,要理解一点:下文中提到到的unicode编码是双字节编码(一个字符两个字节)。
uft8:对于ascii编码的那些字符,只需要1个字节,unicode给这些字符也设定2个字节,如果一篇文章全是英文(ascii字符),就浪费了很多空间(本来1个字节可以存储的,用了2个字节),所以产生了utf8。utf8是一种变长的编码方式,根据不同的符号变化字节长度,把ascii编码成1个字节,汉字通常编码成3个字节,一些生僻的字符编码成4~6个字节。
在计算机内存中,统一使用Unicode编码。
在python中,建议程序过程中统一使用unicode编码,保存文件和读取文件时使用utf8(在读写磁盘文件时候用utf8进行相应的decode和encode,关于decode和encode见下文第4点)。
2. encoding声明
python默认使用ascii编码去解释源文件。
如果源文件中出现了非ASCII码字符,不在开头声明encoding会报错。
可以声明为utf8,告诉解释器用utf8去读取文件代码,这个时候源文件有中文也不会报错。
# encoding=utf8 如果不加这一行会报错
print '解释器用相应的encoding去解释python代码'
3. python2.7中的str和unicode
debugger的时候会发现,python2.7中的字符串一般有两种类型,unicode和str。
str为字节码,会根据某种编码把字符串转成一个个字节,这个时候字符和字节没有所谓固定的一一对应的关系。
unicode则是用unicode编码的字符串,这个时候一个字符是对应两个字节的,一一对应。
直接赋值字符串,类型为str,str为字节串,会按照开头的encoding来编码成一个个的字节。
赋值的时候在字符串前面加个u,类型则为unicode,直接按照unicode来编码。
s1 = '字节串'
print type(s1) #输出 <type 'str'>,按照开头的encoding来编码成相应的字节。
print len(s1) #输出9,因为按utf8编码,一个汉字占3个字节,3个字就占9个字节。
s2 = u'统一码'
print type(s2) #输出 <type 'unicode'>,用unicode编码,2个字节1个字符。
print len(s2) #输出3,unicode用字符个数来算长度,从这个角度上看,unicode才是真正意义上的字符串类型
来看点现实的例子,比如我们要从一个文件中找出中所有后两位是'学习'的词语,在进行判断的时候:
s = '机器学习'
s[-2:] == '学习‘ # 返回false,平时写程序可能会以为相等
s = u'机器学习'
s[-2:] == u'学习’ # 返回true,这也是为什么说unicode是真正意义上的字符串类型
对于经常处理中文字符串的人,统一用unicode就可以避免这个坑了。
虽然有些字符串处理函数用str也可以,我猜是函数里面帮你处理了编码问题,不能因为这个抱着侥幸心态。
强迫症:要统一就全部统一,不喜欢一半用unicode,一半用str。
4. python2.7中的encode和decode
encode的正常使用:对unicode类型进行encode,得到字节串str类型。也即是unicode -> encode(根据指定编码) -> str。
decode的正常使用:对str类型进行decode,得到unicode类型。也即是str -> decode(根据指定编码) -> unicode。
注意:encode和decode的时候都是需要指定编码的。
因为在编码的时候要知道原来的编码是什么和按照什么新编码方式进行编码,要用到两种编码,这里默认有一个unicode,所以需要再指定一个编码方式。解码的时候也是一个道理。
这两个方法就是在unicode和str之间用指定编码进行转换。
s3 = u'统一码'.encode('utf8')
print type(s3) # 输出 <type 'str'>
s4 = '字节串'.decode('utf8')
print type(s4) #输出 <type 'unicode'>
encode的不正常使用:对str类型进行encode,因为encode需要的是unicode类型,这个时候python会用默认的系统编码decode成unicode类型,再用你给出编码进行encode。(注意这里的系统编码不是开头的encoding,具体例子见下文第5点)
decode的不正常使用:对unicode类型进行decode,python会用默认的系统编码encode成str类型,再用你给出的编码进行decode。
所以改好对应的系统默认编码,就算不正常使用,也不会报错啦。不过多拐了一下路,个人不喜欢这样。
5. 修改系统默认编码
系统默认使用ascii编码,需要进行相应的修改。
这个编码和开头的encoding不同之处在于,开头的encoding是对于文件内容的编码。
这里的编码是一些python方法中默认使用的编码,比如对str进行encode的时候默认先decode的编码,比如文件写操作write的encode的编码(关于文件读写见下文第7点)
import sys
reload(sys)
sys.setdefaultencoding('utf8')
s = '字节串str'
s.encode('utf8')
#等价于
s.decode(系统编码).encode('utf8')
关于系统默认编码发挥作用的地方,来看看另一个例子。
import sys
print sys.getdefaultencoding() # 输出ascii
s = 'u华南理工大学'
print s[-2:] == '大学' # 返回False,并有warning提醒
reload(sys)
s