Python:字符编码详解

相关文章

Python中文编码问题:为什么在控制台下输出中文会乱码及其原理html

1. 字符编码简介

1.1. ASCII

ASCII(American Standard Code for Information Interchange),是一种单字节的编码。计算机世界里一开始只有英文,而单字节能够表示256个不一样的字符,能够表示全部的英文字符和许多的控制符号。不过ASCII只用到了其中的一半(\x80如下),这也是MBCS得以实现的基础。python

1.2. MBCS

然而计算机世界里很快就有了其余语言,单字节的ASCII已没法知足需求。后来每一个语言就制定了一套本身的编码,因为单字节能表示的字符太少,并且同时也须要与ASCII编码保持兼容,因此这些编码纷纷使用了多字节来表示字符,如GBxxxBIGxxx等等,他们的规则是,若是第一个字节是\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。post

1.3. Unicode

后来,有人开始以为太多编码致使世界变得过于复杂了,让人脑壳疼,因而你们坐在一块儿拍脑壳想出来一个方法:全部语言的字符都用同一种字符集来表示,这就是Unicode。测试

最初的Unicode标准UCS-2使用两个字节表示一个字符,因此你经常能够听到Unicode使用两个字节表示一个字符的说法。但过了不久有人以为256*256太少了,仍是不够用,因而出现了UCS-4标准,它使用4个字节表示一个字符,不过咱们用的最多的仍然是UCS-2。this

UCS(Unicode Character Set)还仅仅是字符对应码位的一张表而已,好比"汉"这个字的码位是6C49。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责。编码

一开始这事很简单,直接使用UCS的码位来保存,这就是UTF-16,好比,"汉"直接使用\x6C\x49保存(UTF-16-BE),或是倒过来使用\x49\x6C保存(UTF-16-LE)。但用着用着美国人以为本身吃了大亏,之前英文字母只须要一个字节就能保存了,如今大锅饭一吃变成了两个字节,空间消耗大了一倍……因而UTF-8横空出世。url

UTF-8是一种很别扭的编码,具体表如今他是变长的,而且兼容ASCII,ASCII字符使用1字节表示。然而这里省了的一定是从别的地方抠出来的,你确定也据说过UTF-8里中文字符使用3个字节来保存吧?4个字节保存的字符更是在泪奔……(具体UCS-2是怎么变成UTF-8的请自行搜索)spa

另外值得一提的是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的区别后,这两个方法就不会再混淆了:

?
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字符,则须要在文件头部进行字符编码的声明,以下:

?
1
#-*- coding: UTF-8 -*-

实际上Python只检查#、coding和编码字符串,其余的字符都是为了美观加上的。另外,Python中可用的字符编码有不少,而且还有许多别名,还不区分大小写,好比UTF-8能够写成u8。参见http://docs.python.org/library/codecs.html#standard-encodings

另外须要注意的是声明的编码必须与文件实际保存时用的编码一致,不然很大概率会出现代码解析异常。如今的IDE通常会自动处理这种状况,改变声明后同时换成声明的编码保存,但文本编辑器控们须要当心 :)

2.3. 读写文件

内置的open()方法打开文件时,read()读取的是str,读取后须要使用正确的编码格式进行decode()。write()写入时,若是参数是unicode,则须要使用你但愿写入的编码进行encode(),若是是其余编码格式的str,则须要先用该str的编码进行decode(),转成unicode后再使用写入的编码进行encode()。若是直接将unicode做为参数传入write()方法,Python将先使用源代码文件声明的字符编码进行编码而后写入。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# coding: UTF-8
 
f = open ( 'test.txt' )
s = f.read()
f.close()
print type (s) # <type 'str'>
# 已知是GBK编码,解码成unicode
u = s.decode( 'GBK' )
 
f = open ( 'test.txt' , 'w' )
# 编码成UTF-8编码的str
s = u.encode( 'UTF-8' )
f.write(s)
f.close()

另外,模块codecs提供了一个open()方法,能够指定一个编码打开文件,使用这个方法打开的文件读取返回的将是unicode。写入时,若是参数是unicode,则使用open()时指定的编码进行编码后写入;若是是str,则先根据源代码文件声明的字符编码,解码成unicode后再进行前述操做。相对内置的open()来讲,这个方法比较不容易在编码上出现问题。

?
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 ( 'test.txt' , encoding = 'UTF-8' )
u = f.read()
f.close()
print type (u) # <type 'unicode'>
 
f = codecs. open ( 'test.txt' , '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模块中提供了一些获取当前环境下的默认编码的方法。

?
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
30
31
# coding:gbk
 
import sys
import locale
 
def p(f):
     print '%s.%s(): %s' % (f.__module__, f.__name__, f())
 
# 返回当前系统所使用的默认字符编码
p(sys.getdefaultencoding)
 
# 返回用于转换Unicode文件名至系统文件名所使用的编码
p(sys.getfilesystemencoding)
 
# 获取默认的区域设置并返回元祖(语言, 编码)
p(locale.getdefaultlocale)
 
# 返回用户设定的文本数据编码
# 文档提到this function only returns a guess
p(locale.getpreferredencoding)
 
# \xba\xba是'汉'的GBK编码
# mbcs是不推荐使用的编码,这里仅做测试代表为何不该该用
print r "'\xba\xba'.decode('mbcs'):" , repr ( '\xba\xba' .decode( 'mbcs' ))
 
#在笔者的Windows上的结果(区域设置为中文(简体, 中国))
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp936')
#locale.getpreferredencoding(): cp936
#'\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小节中的代码的结果:

?
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
#中文(简体, 中国)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp936')
#locale.getpreferredencoding(): cp936
#'\xba\xba'.decode('mbcs'): u'\u6c49'
 
#英语(美国)
#sys.getdefaultencoding(): UTF-8
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp1252')
#locale.getpreferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'
 
#德语(德国)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp1252')
#locale.getpreferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'
 
#日语(日本)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp932')
#locale.getpreferredencoding(): 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--

文章转载自:http://www.cnblogs.com/huxi/archive/2010/12/05/1897271.html

相关文章
相关标签/搜索