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