19.python的编码问题
在正式说明之前,先给大家一个参考资料:戳这里
文章的内容参考了这篇资料,并加以总结,为了避免我总结的不够完善,或者说出现什么错误的地方,有疑问的地方大家可以看看上面那篇文章。
以下说明是针对于python2.x版本,因为在python3.x中默认使用的是Unicode。
下面开始讲python中的编码问题,首先,我们看看编码有哪些。
1. ASCII
ASCII是用一个字节表示字符,而一个字节由八位二进制组成,所以能产生2**8=256种变化,在计算机刚诞生的年代,用来表示大小写的26个英文字母,外加一些符号之类的还是绰绰有余的。这也是python2.x中默认使用的编码,所以在python2.x中默认不能使用中文,除非使用编码声明。
2. MBCS
随着时代的发展,ASCII就太够用了,计算机不能只显示英文吧,那样实在太low。此时,大家看到ASCII码中还有没用完的,所以都想占用剩下的部分,但是剩下的部分还是不够,例如我们中文那么多肯定是不够用的,所以此时又扩展了一下,一个字节不行,我就用两个。而又为了兼容ASCII码,有定义了这样一套规则:如果第一个字节是\x80以下,则仍然表示ASCII字符;而如果是\x80以上,则跟下一个字节一起(共两个字节)表示一个字符,然后跳过下一个字节,继续往下判断。例如GB...和BIG...之类的都遵循这样的规则。
但是,这样还是实在太乱了,此时IBM跳了出来,大喊一声:这些东西都要统一进行管理!!所以弄出了代码页的概念,将这些字符集都收录了起来,并进行了分页,而这些分页的总称就叫MBCS,例如GBK在936页,所以又叫cp936。而大家都是使用的双字节,所以也称为DBCS。
但很明显,MBCS里面收集和各样的字符集,但是你不能说你要使用MBCS这个字符集编码,里面存了怎么多种,到底是要用哪种,你不说清楚我总不能随机给你一种吧。所以必须要进行指定,但是这个工作已经由操作系统自己完成了(linux不支持),而操作系统有时根据地区的不同而选择的。例如简体中文版的,就选GBK,其他国家的又会有不同,具体按版本而定。所以,一旦在python的编码声明中使用MBCS/BDCS,在进行过系统或跨地区运行的时候,报错也是在所难免的。所以编码声明中一定要具体的指定,例如我们常用的utf-8,这样就不会因为系统和地区的差异而造成各种编码的错误。
在windows中,微软又为它起了个别名,叫ANSI,其实就是MBSC,大家知道就好了。
3.Unicode
虽然MBSC一定程度上解决了编码混乱的问题,但还是特点的编码只能显示特点的字符。这样要开发一种适配多国语言的程序就变得非常困难,此时人们在想,有没有一种编码能搞到所以的字符。大家研究了一番之后,Unicode就此诞生。干脆大家都不要在ASCII上拓展来拓展去,搞得各种版本如此混乱。以后大家都用两个字节保存算了,这样就有了256*256=65536种字符可以表示了,总归是够用了吧。这就是UCS-2标准了。后来还有人说不够用的,那么干脆翻一倍,用四个字节表示,256**4=4294967296,就算将来表示外星文字也能撑一段时间了吧。当然现在常用的还是UCS-2标准的。
UCS(Unicode Character Set)还仅仅是字符对应码位的一张表而已(也就是表示字节),比如"汉"这个字的码位是6C49。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责(也就是保存字节)。(注意:表示字节≠保存字节,也就是虽然我用了2个字节表示字符,但是我保存的时候不一定就直接保存用来表示的那个字节)
刚开始都是直接使用UCS的码位来保存,这就是UTF-16,比如,"汉"直接使用\x6C\x49保存(UTF-16-BE),或是倒过来使用\x49\x6C保存(UTF-16-LE)。但美国佬后来不愿意了,我原来用ASCII只有1个字节就能搞到,现在却要两个字节,足足长了一倍呀。一倍是什么概念,四舍五入那是将近一个亿呀。真当我磁盘空间不用钱呀,为了满足这个述求,就诞生了UTF-8。
UTF-8是一种很别扭的编码,具体表现在他是变长的,并且兼容ASCII,ASCII字符使用1字节表示。但有得必有失,在UTF-8中,东亚的文字是用三个字节表示的,包括中文,一些不常用的字符更是用四个字节表示。于是别的国家保存的成本变高了,而美国佬却变低了。又再次坑了别人,满足了自己。但是没办法,谁叫人家是计算机界的老大呢?
什么是BOM
当一个文本编辑要打开一个文件时,它表示懵逼了。世间编码如此之多,我究竟要用什么哪种编码去解码呀?你总得告诉我吧!
此时,UTF就进入了BOM来表示编码。所谓的BOM就是文件使用编码的标识符,就和python的编码声明一样,告诉文本编辑器我用的是什么编码,下面的你都用那个编码去解码就行。
同样的,只有文本编辑器在文件开头的地方读到了关于BOM的描述,就能够进行正确的界面了。
下面是一些BOM的总结:
BOM_UTF8 '\xef\xbb\xbf'
BOM_UTF16_LE '\xff\xfe'
BOM_UTF16_BE '\xfe\xff'
同样了,为了我们自己编辑的文件的编码也能被正确识别,我们也要写入BOM,一般由编辑器完成。但不写也可以,只有在打开文件的时候自己手动选择用什么去解码也是可以的。
但是,还有一种叫UTF-8无BOM模式的,这又是什么鬼。
因为UTF-8实在太流行了,所以文本编辑器默认会首先用UTF-8进行解码。即使是保存时默认使用ANSI(MBCS)的记事本,在读取文件时也是先使用UTF-8测试编码,如果可以成功解码,则使用UTF-8解码。记事本这个别扭的做法造成了一个BUG:如果你新建文本文件并输入"姹塧"然后使用ANSI(MBCS)保存,再打开就会变成"汉a"。)
下用一幅图来总结:
此时,有些人会在MBCS和UCS-2之间迷糊,大家都是两个字节表示,又有什么不同?
MBCS是各自拓展的,有就是说很可能相同的二进制表示MBCS会出现不同的结果,而Unicode是统一拓展,保证了每种二进制表示都对应唯一一个字符,保证了唯一性,也就提高了兼容能力。
ok,在讲完字符编码的问题之后,现在再来看一下:
# coding:gbk 和 # coding= utf-8 之类的编码声明对python而言到底意味着什么。
这里插播一个小技巧:
# coding : utf-8 或者这样 # coding = utf-8 的声明方式是会报错的,这里并不是说是特点的=或者:的问题,而是空格的问题,在coding和符号之间是不能有空格的,但在符号和utf-8之类的编码名称间是运行0个或多个空格的,#和coding间也是运行0个或多个空格的。我也不知道为什么,但实际就是报错了。
#! /usr/bin/env python #coding = utf-8 print '中文'
这里coding和=号一个空格:
报错了。
#! /usr/bin/env python #coding= utf-8 print '中文'
coding和=之间没空格:
正常执行。
不清楚是我IDE的问题还是python本来的语法是这样规定的,但其实很少有地方谈及这个地方的语法,所以这里提及一下,最后自己实验一下。
# -*- coding: utf-8 -*- 的写法也是一样的。
好了,一下进入正题:
#! /usr/bin/env python # coding= utf-8 print '中文' print str('中文') print repr('中文') print repr(u'中文')
这里顺便解释一下str()函数和repr()函数在创建或转换字符串上的区别,str()得到是一个对人类可读性比较好的字符串,而print也是默认调用这个函数的。而repr()是创建或转换字符串时,得到是对机器可读性更好的字符串,就是这里得到是其编码。
下面是另一种编码的输出:
#! /usr/bin/env python # coding= gbk print '中文' print str('中文') print repr('中文') print repr(u'中文')
前面两个是乱码,不过这里是我IDE的问题,我的IDE默认是用UTF-8的。
这里改成GBK再试试。
又可以了。
这里再次引入一个问题,什么是文件保存编码,什么是代码运行编码。
还是不能,那再改一下。
这样又可以了。
其实保存编码,也就是IDE Encoding设置的是在磁盘中保存和打开的时候的编码。假设我使用的是windows的文本编辑器写的代码,也就是用ANSI保存的,如何我再用其他编码打开就会出现乱码,这无关运行的事。而运行编码是则是影响python交互界面的编码,我在前面的python的第一个程序中也说过这个问题,我用windows的cmd运行一个python文件,为什么我在python中声明了可以使用中文的utf-8,却在输出显示的时候还是出现了乱码?这是因为虽然python输出的utf-8的编码,但是cmd这个家伙默认是用GBK去解码的,所以出现了显示上的乱码。但这并不是真正的乱码,换用一个支持utf-8的显示环境就可以了。
好,题外话多了些,但这些小的错误可能会让人纠结很久都解决不了,所以这里还是提及一下的好。
下面进入正题,看下面的现象:
utf-8:
gbk:
我们可以发现,随着python编码声明的不同,其字符串的编码也不同,所以我们得出一个结论:
python中的编码声明影响的是普通字符串的编码,也是就用工程函数str()或者单纯的引号创建的出来的字符串。是随着编码声明的不同而不同的。
此时再看一下这个现象:
发现Unicode字符串无论在上面情况下都是一样的,因为它永远采用的是Unicode进行编码,不管你声明的到底是什么。
Unicode字符串的创建,也就是Unicode对象的创建可以使用工厂函数unicode()或者在引号前面加个u。
所以,我们可以得出一个在python进行编码转换的规则:
再总体扩展一下:
只要python支持的字符集,都能够用这样的逻辑在python的内部进行编码转换。
只要知道了这些,那么在文件处理中就能避免许多错误了,关于python的文件处理我们下篇再讲。