python编码格式
1. 字符编码简介
1.1. ASCII
ASCII(American Standard Code for Information Interchange),是⼀种单字节的编码。计算机世界⾥⼀开始只有英⽂,⽽单字节可以表⽰256个不同的字符,可以表⽰所有的英⽂字符和许多的控制符号。不过ASCII只⽤到了其中的⼀半(\x80以下),这也是MBCS得以实现的基础。
1.2. MBCS
然⽽计算机世界⾥很快就有了其他语⾔,单字节的ASCII已⽆法满⾜需求。后来每个语⾔就制定了⼀套⾃⼰的编码,由于单字节能表⽰的字符太少,⽽且同时也需要与ASCII编码保持兼容,所以这些编码纷纷使⽤了多字节来表⽰字符,如GBxxx、BIGxxx等等,他们的规则是,如果第⼀个字节是\x80以下,则仍然表⽰ASCII字符;⽽如果是\x80以上,则跟下⼀个字节⼀起(共两个字节)表⽰⼀个字符,然后跳过下⼀个字节,继续往下判断。
这⾥,IBM发明了⼀个叫Code Page的概念,将这些编码都收⼊囊中并分配页码,GBK是第936页,也就是CP936。所以,也可以使⽤
CP936表⽰GBK。
MBCS(Multi-Byte Character Set)是这些编码的统称。⽬前为⽌⼤家都是⽤了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。必须明确的是,MBCS并不是某⼀种特定的编码,Windows⾥根据你设定的区域不同,MBCS指代不同的编码,⽽Linux⾥⽆法使⽤MBCS作为编码。在Windows中你看不到MBCS这⼏个字符,因为微软为了更加洋⽓,使⽤了ANSI来吓唬⼈,记事本的另存为对话框⾥编码ANSI就是MBCS。同时,在简体中⽂Windows默认的区域设定⾥,指代GBK。
1.3. Unicode
后来,有⼈开始觉得太多编码导致世界变得过于复杂了,让⼈脑袋疼,于是⼤家坐在⼀起拍脑袋想出来⼀个⽅法:所有语⾔的字符都⽤同⼀种字符集来表⽰,这就是Unicode。
最初的Unicode标准UCS-2使⽤两个字节表⽰⼀个字符,所以你常常可以听到Unicode使⽤两个字节表⽰⼀个字符的说法。但过了不久有⼈觉得256*256太少了,还是不够⽤,于是出现了UCS-4标准,它使⽤4个字节表⽰⼀个字符,不过我们⽤的最多的仍然是UCS-2。
UCS(Unicode Character Set)还仅仅是字符对应码位的⼀张表⽽已,⽐如”汉”这个字的码位是6C49。字符具体如何传输和储存则是由
UTF(UCS Transformation Format)来负责。
⼀开始这事很简单,直接使⽤UCS的码位来保存,这就是UTF-16,⽐如,”汉”直接使⽤\x6C\x49保存(UTF-16-BE),或是倒过来使⽤
\x49\x6C保存(UTF-16-LE)。但⽤着⽤着美国⼈觉得⾃⼰吃了⼤亏,以前英⽂字母只需要⼀个字节就能保存了,现在⼤锅饭⼀吃变成了两个字节,空间消耗⼤了⼀倍……于是UTF-8横空出世。
UTF-8是⼀种很别扭的编码,具体表现在他是变长的,并且兼容ASCII,ASCII字符使⽤1字节表⽰。然⽽这⾥省了的必定是从别的地⽅抠出来的,你肯定也听说过UTF-8⾥中⽂字符使⽤3个字节来保存吧?4个字节保存的字符更是在泪奔……(具体UCS-2是怎么变成UTF-8的请⾃⾏搜索)
另外值得⼀提的是BOM(Byte Order Mark)。我们在储存⽂件时,⽂件使⽤的编码并没有保存,打开时则需要我们记住原先保存时使⽤的编码并使⽤这个编码打开,这样⼀来就产⽣了许多⿇烦。(你可能想说记事本打开⽂件时并没有让选编码?不妨先打开记事本再使⽤⽂件 -> 打开看看)⽽UTF则引⼊了BOM来表⽰⾃⾝编码,如果⼀开始读⼊的⼏个字节是其中之⼀,则代表接下来要读取的⽂字使⽤的编码是相应的编码:
BOM_UTF8 ‘\xef\xbb\xbf’
BOM_UTF16_LE ‘\xff\xfe’
BOM_UTF16_BE ‘\xfe\xff’
并不是所有的编辑器都会写⼊BOM,但即使没有BOM,Unicode还是可以读取的,只是像MBCS的编码⼀样,需要另⾏指定具体的编码,否则解码将会失败。
你可能听说过UTF-8不需要BOM,这种说法是不对的,只是绝⼤多数编辑器在没有BOM时都是以UTF-8作为默认编码读取。即使是保存时默认使⽤ANSI(MBCS)的记事本,在读取⽂件时也是先使⽤UTF-8测试编码,如果可以成功解码,则使⽤UTF-8解码。记事本这个别扭的做法造成了⼀个BUG:如果你新建⽂本⽂件并输⼊”姹塧”然后使⽤ANSI(MBCS)保存,再打开就会变成”汉a”,你不妨试试:)
2. Python2.x中的编码问题
2.1. str和unicode
str和unicode都是basestring的⼦类。严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列。对UTF-8编码的
str’汉’使⽤len()函数时,结果是3,因为实际上,UTF-8编码的’汉’ == ‘\xE6\xB1\x89’。
unicode才是真正意义上的字符串,对字节串str使⽤正确的字符编码进⾏解码后获得,并且len(u’汉’) == 1。
再来看看encode()和decode()两个basestring的实例⽅法,理解了str和unicode的区别后,这两个⽅法就不会再混淆了:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13# coding: UTF-8
u = u'汉'
print repr(u) # u'\u6c49'
s = u.encode('UTF-8')
print repr(s) # '\xe6\xb1\x89'
u2 = s.decode('UTF-8')
print repr(u2) # u'\u6c49'
# 对unicode进⾏解码是错误的
# s2 = u.decode('UTF-8')
# 同样,对str进⾏编码也是错误的# u2 = s.encode('UTF-8')
需要注意的是,虽然对str调⽤encode()⽅法是错误的,但实际上Python不会抛出异常,⽽是返回另外⼀个相同内容但不同id的str;对unicode调⽤decode()⽅法也是这样。很不理解为什么不把encode()和decode()分别放在unicode和str中⽽是都放在basestring中,但既然已经这样了,我们就⼩⼼避免犯错吧。
2.2. 字符编码声明
源代码⽂件中,如果有⽤到⾮ASCII字符,则需要在⽂件头部进⾏字符编码的声明,如下:
#-*- coding: UTF-8 -*-
实际上Python只检查#、coding和编码字符串,其他的字符都是为了美观加上的。另外,Python中可⽤的字符编码有很多,并且还有许多别名,还不区分⼤⼩写,⽐如UTF-8可以写成u8。参见/library/codecs.html#standard-encodings。
另外需要注意的是声明的编码必须与⽂件实际保存时⽤的编码⼀致,否则很⼤⼏率会出现代码解析异常。现在的IDE⼀般会⾃动处理这种情况,改变声明后同时换成声明的编码保存,但⽂本编辑器控们需要⼩⼼:)
2.3. 读写⽂件
内置的open()⽅法打开⽂件时,read()读取的是str,读取后需要使⽤正确的编码格式进⾏decode()。write()写⼊时,如果参数是unicode,则需要使⽤你希望写⼊的编码进⾏encode(),如果是其他编码格式的str,则需要先⽤该str的编码进⾏decode(),转成unicode后再使⽤写⼊的编码进⾏encode()。如果直接将unicode作为参数传⼊write()⽅法,Python将先使⽤源代码⽂件声明的字符编码进⾏编码然后写⼊。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14# coding: UTF-8
f = open('')
s = f.read()
f.close()
print type(s) # <type 'str'>
# 已知是GBK编码,解码成unicode u = s.decode('GBK')
f = open('', 'w')
# 编码成UTF-8编码的str
s = u.encode('UTF-8')
f.write(s)
f.close()
另外,模块codecs提供了⼀个open()⽅法,可以指定⼀个编码打开⽂件,使⽤这个⽅法打开的⽂件读取返回的将是unicode。写⼊时,如果参数是unicode,则使⽤open()时指定的编码进⾏编码后写⼊;如果是str,则先根据源代码⽂件声明的字符编码,解码成unicode后再进⾏前述操作。相对内置的open()来说,这个⽅法⽐较不容易在编码上出现问题。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20# coding: GBK
import codecs
f = codecs.open('', encoding='UTF-8')
u = f.read()
f.close()
print type(u) # <type 'unicode'>
f = codecs.open('', 'a', encoding='UTF-8')
# 写⼊unicode
f.write(u)
# 写⼊str,⾃动进⾏解码编码操作
# GBK编码的str
s = '汉'
print repr(s) # '\xba\xba'
# 这⾥会先将GBK编码的str解码为unicode再编码为UTF-8写⼊f.write(s)
f.close()
2.4. 与编码相关的⽅法
sys/locale模块中提供了⼀些获取当前环境下的默认编码的⽅法。Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29# coding:gbk
import sys
import locale
def p(f):
print '%s.%s(): %s' % (f.__module__, f.__name__, f())
# 返回当前系统所使⽤的默认字符编码
defaultencoding)
# 返回⽤于转换Unicode⽂件名⾄系统⽂件名所使⽤的编码
filesystemencoding)
# 获取默认的区域设置并返回元祖(语⾔, 编码)
defaultlocale)
# 返回⽤户设定的⽂本数据编码
# ⽂档提到this function only returns a guess
preferredencoding)
python中的字符串是什么# \xba\xba是'汉'的GBK编码
# mbcs是不推荐使⽤的编码,这⾥仅作测试表明为什么不应该⽤print r"'\xba\xba'.decode('mbcs'):", repr('\xba\xba'.decode('mbcs')) #在笔者的Windows上的结果(区域设置为中⽂(简体, 中国))
#defaultencoding(): gbk
#filesystemencoding(): mbcs
#defaultlocale(): ('zh_CN', 'cp936')
31#'\xba\xba'.decode('mbcs'): u'\u6c49'
3.⼀些建议
3.1. 使⽤字符编码声明,并且同⼀⼯程中的所有源代码⽂件使⽤相同的字符编码声明。
这点是⼀定要做到的。
3.2. 抛弃str,全部使⽤unicode。
按引号前先按⼀下u最初做起来确实很不习惯⽽且经常会忘记再跑回去补,但如果这么做可以减少90%的编码问题。如果编码困扰不严重,可以不参考此条。
3.3. 使⽤codecs.open()替代内置的open()。
如果编码困扰不严重,可以不参考此条。
3.4. 绝对需要避免使⽤的字符编码:MBCS/DBCS和UTF-16。
这⾥说的MBCS不是指GBK什么的都不能⽤,⽽是不要使⽤Python⾥名为’MBCS’的编码,除⾮程序完全不移植。
Python中编码’MBCS’与’DBCS’是同义词,指当前Windows环境中MBCS指代的编码。Linux的Python实现中没有这种编码,所以⼀旦移植到Linux⼀定会出现异常!另外,只要设定的Windows系统区域不同,MBCS指代的编码也是不⼀样的。分别设定不同的区域运⾏2.4⼩节中的代码的结果:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27#中⽂(简体, 中国)
#defaultencoding(): gbk
#filesystemencoding(): mbcs
#defaultlocale(): ('zh_CN', 'cp936') #preferredencoding(): cp936
#'\xba\xba'.decode('mbcs'): u'\u6c49'
#英语(美国)
#defaultencoding(): UTF-8
#filesystemencoding(): mbcs
#defaultlocale(): ('zh_CN', 'cp1252') #preferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'
#德语(德国)
#defaultencoding(): gbk
#filesystemencoding(): mbcs
#defaultlocale(): ('zh_CN', 'cp1252') #preferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'
#⽇语(⽇本)
#defaultencoding(): gbk
#filesystemencoding(): mbcs
#defaultlocale(): ('zh_CN', 'cp932') #preferredencoding(): cp932
#'\xba\xba'.decode('mbcs'): u'\uff7a\uff7a'
可见,更改区域后,使⽤mbcs解码得到了不正确的结果,所以,当我们需要使⽤’GBK’时,应该直接写’GBK’,不要写成’MBCS’。
UTF-16同理,虽然绝⼤多数操作系统中’UTF-16’是’UTF-16-LE’的同义词,但直接写’UTF-16-LE’只是多写3个字符⽽已,⽽万⼀某个操作系统中’UTF-16’变成了’UTF-16-BE’的同义词,就会有错误的结果。实际上,UTF-16⽤的相当少,但⽤到的时候还是需要注意。
–END–

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。