python編碼問題

幾個基本概念

bit
二進(jìn)制位, 是計(jì)算機(jī)內(nèi)部數(shù)據(jù)儲存的最小單位,11010100是一個8位二進(jìn)制數(shù)。一個二進(jìn)制位只可以表示0和1兩種狀態(tài)(21);兩個二進(jìn)制位可以表示00、01、10、11四種(22)狀態(tài);三位二進(jìn)制數(shù)可表示八種狀態(tài)(2^3)……

Byte
字節(jié),是計(jì)算機(jī)中數(shù)據(jù)處理的基本單位,計(jì)算機(jī)中以字節(jié)為單位存儲和解釋信息,規(guī)定一個字節(jié)由八個二進(jìn)制位構(gòu)成,即1個字節(jié)等于8個比特(1Byte=8bit)。八位二進(jìn)制數(shù)最小為00000000,最大為11111111;通常1個字節(jié)可以存入一個ASCII碼,2個字節(jié)可以存放一個漢字國標(biāo)碼。


在計(jì)算機(jī)中,一串?dāng)?shù)碼作為一個整體來處理或運(yùn)算的,稱為一個計(jì)算機(jī)字,簡稱宇。字通常分為若干個字節(jié)(每個字節(jié)一般是8位)。在存儲器中,通常每個單元存儲一個字,因此每個字都是可以尋址的。字的長度用位數(shù)來表示。在計(jì)算機(jī)的運(yùn)算器、控制器中,通常都是以字為單位進(jìn)行傳送的。

字長
字長:電腦技術(shù)中對CPU在單位時間內(nèi)(同一時間)能一次處理的二進(jìn)制數(shù)的位數(shù)叫字長。所以能處理字長為8位數(shù)據(jù)的CPU通常就叫8位的CPU。同理32位的CPU就能在單位時間內(nèi)處理字長為32位的二進(jìn)制數(shù)據(jù)。

字節(jié)和字長的區(qū)別:由于常用的英文字符用8位二進(jìn)制就可以表示,所以通常就將8位稱為一個字節(jié)。字長的長度是不固定的,對于不同的CPU、字長的長度也不一樣。8位的CPU一次只能處理一個字節(jié),而32位的CPU一次就能處理4個字節(jié),同理字長為64位的CPU一次可以處理8個字節(jié)。

常見的字符編碼

為什么Python使用過程中會出現(xiàn)各式各樣的亂碼問題,明明是中文字符卻顯示成“/xe4/xb8/xad/xe6/x96/x87”的形式?為什么會報(bào)錯“UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)”?本文就來研究一下這個問題。

編解碼過程

字符串在Python內(nèi)部的表示是unicode編碼,因此,在做編碼轉(zhuǎn)換時,通常需要以unicode作為中間編碼,即先將其他編碼的字符串解碼(decode)成unicode,再從unicode編碼(encode)成另一種編碼。
decode的作用是將其他編碼的字符串轉(zhuǎn)換成unicode編碼,如str1.decode('gb2312'),表示將gb2312編碼的字符串str1轉(zhuǎn)換成unicode編碼。
encode的作用是將unicode編碼轉(zhuǎn)換成其他編碼的字符串,如str2.encode('gb2312'),表示將unicode編碼的字符串str2轉(zhuǎn)換成gb2312編碼。
因此,轉(zhuǎn)碼的時候一定要先搞明白,字符串str是什么編碼,然后decode成unicode,然后再encode成其他編碼

整個過程如下,即
? decode ???? encode
str ---------> unicode --------->str

u = u'中文' #顯示指定unicode類型對象u
str = u.encode('gb2312') #以gb2312編碼對unicode對像進(jìn)行編碼
str1 = u.encode('gbk') #以gbk編碼對unicode對像進(jìn)行編碼
str2 = u.encode('utf-8') #以utf-8編碼對unicode對像進(jìn)行編碼
u1 = str.decode('gb2312')#以gb2312編碼對字符串str進(jìn)行解碼,以獲取unicode
u2 = str.decode('utf-8')#如果以utf-8的編碼對str進(jìn)行解碼得到的結(jié)果,將無法還原原來的unicode類型

如上面代碼,str\str1\str2均為字符串類型(str),給字符串操作帶來較大的復(fù)雜性。

好消息來了,對,那就是python3,在新版本的python3中,取消了unicode類型,代替它的是使用unicode字符的字符串類型(str),字符串類型(str)成為基礎(chǔ)類型如下所示,而編碼后的變?yōu)榱俗止?jié)類型(bytes)但是兩個函數(shù)的使用方法不變:
? ? decode ?????? encode
bytes ---------> str(unicode) --------->bytes

u = '中文' #指定字符串類型對象u
str = u.encode('gb2312') #以gb2312編碼對u進(jìn)行編碼,獲得bytes類型對象str
u1 = str.decode('gb2312')#以gb2312編碼對字符串str進(jìn)行解碼,獲得字符串類型對象u1
u2 = str.decode('utf-8')#如果以utf-8的編碼對str進(jìn)行解碼得到的結(jié)果,將無法還原原來的字符串內(nèi)容

在文件讀取的過程中:
假如我們讀取一個文件,文件保存時,使用的編碼格式,決定了我們從文件讀取的內(nèi)容的編碼格式,例如,我們從記事本新建一個文本文件test.txt, 編輯內(nèi)容,保存的時候注意,編碼格式是可以選擇的,例如我們可以選擇gb2312,那么使用python讀取文件內(nèi)容,方式如下:

f = open('test.txt','r')
s = f.read() #讀取文件內(nèi)容,如果是不識別的encoding格式(識別的encoding類型跟使用的系統(tǒng)有關(guān)),這里將讀取失敗
'''假設(shè)文件保存時以gb2312編碼保存'''
u = s.decode('gb2312') #以文件保存格式對內(nèi)容進(jìn)行解碼,獲得unicode字符串
'''下面我們就可以對內(nèi)容進(jìn)行各種編碼的轉(zhuǎn)換了'''
str = u.encode('utf-8')#轉(zhuǎn)換為utf-8編碼的字符串str
str1 = u.encode('gbk')#轉(zhuǎn)換為gbk編碼的字符串str1
str1 = u.encode('utf-16')#轉(zhuǎn)換為utf-16編碼的字符串str1

python給我們提供了一個包c(diǎn)odecs進(jìn)行文件的讀取,這個包中的open()函數(shù)可以指定編碼的類型:

import codecs
f = codecs.open('text.text','r+',encoding='utf-8')#必須事先知道文件的編碼格式,這里文件編碼是使用的utf-8
content = f.read()#如果open時使用的encoding和文件本身的encoding不一致的話,那么這里將將會產(chǎn)生錯誤
f.write('你想要寫入的信息')
f.close()

代碼中字符串的默認(rèn)編碼與代碼文件本身的編碼一致。

如:s='中文'
如果是在utf8的文件中,該字符串就是utf8編碼,如果是在gb2312的文件中,則其編碼為gb2312。這種情況下,要進(jìn)行編碼轉(zhuǎn)換,都需要先用decode方法將其轉(zhuǎn)換成unicode編碼,再使用encode方法將其轉(zhuǎn)換成其他編碼。通常,在沒有指定特定的編碼方式時,都是使用的系統(tǒng)默認(rèn)編碼創(chuàng)建的代碼文件。
如果字符串是這樣定義:s=u'中文'
則該字符串的編碼就被指定為unicode了,即Python的內(nèi)部編碼,而與代碼文件本身的編碼無關(guān)。因此,對于這種情況做編碼轉(zhuǎn)換,只需要直接使用encode方法將其轉(zhuǎn)換成指定編碼即可。

如果一個字符串已經(jīng)是unicode了,再進(jìn)行解碼則將出錯,因此通常要對其編碼方式是否為unicode進(jìn)行判斷:
isinstance(s, unicode) #用來判斷是否為unicode
用非unicode編碼形式的str來encode會報(bào)錯

  • 如何獲得系統(tǒng)的默認(rèn)編碼
#!/usr/bin/env python
#coding=utf-8
import sys
print sys.getdefaultencoding()  
#!/usr/bin/env python  
#coding=utf-8  
s="中文"  
if isinstance(s, unicode):  
#s=u"中文"  
    print s.encode('gb2312')  
else:  
#s="中文"  
    print s.decode('utf-8').encode('gb2312')  

IDE和python2編碼相關(guān)問題

在某些IDE中,字符串的輸出總是出現(xiàn)亂碼,甚至錯誤,其實(shí)是由于IDE的結(jié)果輸出控制臺自身不能顯示字符串的編碼,而不是程序本身的問題。

如在Sublime Text中運(yùn)行如下代碼:

s=u"中文"
print s

會提示:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)。
而同樣的 print u'中文' 代碼在 Mac 的終端里卻能正常打印出 “中文” 結(jié)果,沒有任何報(bào)錯。
這是因?yàn)镾ublime Text在英文win7上的控制臺信息輸出窗口是按照ascii編碼輸出的(英文系統(tǒng)的默認(rèn)編碼是ascii),而上面代碼中的字符串是Unicode編碼的,所以輸出時產(chǎn)生了錯誤。
若最后一句改為:print s.encode('utf8')

則輸出:中文
unicode(str,'gb2312')與str.decode('gb2312')是一樣的,都是將gb2312編碼的str轉(zhuǎn)為unicode編碼
使用str.__class__可以查看str的編碼形式

分析

Python 在向控制臺 (console) print 的時候,因?yàn)榭刂婆_只能看得懂由 bytes(字節(jié)序列)組成的字符串,而 Python 中 "unicode" 對象存儲的是 code points(碼點(diǎn)),因此 Python 需要將輸出中的 "unicode" 對象用編碼轉(zhuǎn)換為儲存 bytes(字節(jié)序列)的 "str" 對象后,才能進(jìn)行輸出。

而在報(bào)錯里看到 UnicodeEncodeError, 那就說明 Python 在將 unicode 轉(zhuǎn)換為 str 時使用了錯誤的編碼。而為什么是 'ascii' 編碼呢?那是因?yàn)?Python 2 的默認(rèn)編碼就是 ASCII,可以通過以下命令來查看 Python 的默認(rèn)編碼:

import sys
print sys.getdefaultencoding()

ascii
所以此時在 Sublime Text 里運(yùn)行 print u'中文',實(shí)際上等于是運(yùn)行了:

print u'中文'.encode('ascii')

ASCII 編碼無法對 unicode 的中文進(jìn)行編碼,因此就報(bào)錯了。
那為什么同樣的代碼 print u'中文' 在 Mac 的終端里卻能正常輸出中文,難道是因?yàn)榻K端下的 Python 2 的默認(rèn)編碼不是 ASCII?非也,在終端下運(yùn)行 sys.getdefaultencoding() 結(jié)果一樣是 ascii。那同樣是 ascii 為什么會有不同的結(jié)果?難倒這里 Python 用了另外一個編碼來轉(zhuǎn)換?

是的,其實(shí) Python 在 print unicode 時真正涉及到的是另一組編碼:stdin/stdout/stderr 的編碼,也就是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出的編碼。可以通過以下命令來查看,這里是在Sublime Text下運(yùn)行的結(jié)果:

import sys
print sys.stdin.encoding
None
print sys.stdout.encoding
None
print sys.stderr.encoding
None

那么在這種 sys.stdout.encoding 為 None 情況下的 print unicode 怎么辦呢?答案就是 Python 只能很無奈地使用 sys.getdefaultencoding() 的默認(rèn)編碼 ascii 來對 unicode 進(jìn)行轉(zhuǎn)換了。這樣就出現(xiàn)了本文開頭所說的那個 UnicodeEncodeError 問題。
在mac下他的這三種輸出都是utf-8,實(shí)際上輸出等于print u'中文'.encode('UTF-8'),所以輸出正常。

python2 向控制臺print輸出是流程

總結(jié)一下 Python 2 向控制臺 print 輸出時的流程:

Python 啟動時,當(dāng)它發(fā)現(xiàn)當(dāng)前的輸出是連接到控制臺的時候,它會根據(jù)一些環(huán)境變量,例如環(huán)境變量LC_CTYPE,來設(shè)法判斷出 sys.stdin/stdout/stderr.encoding 編碼值。
當(dāng) Python 無法判斷出所需的編碼時,它會將 sys.stdin/stdout/stderr.encoding 的值設(shè)置為None。
print 時判斷字符串是否是 unicode 類型。
如果是的話,并且 sys.stdout.encoding 不為 None 時,就使用 sys.stdout.encoding 編碼對 unicode 編碼成 str 后輸出。
如果 sys.stdout.encoding 為 None 的話,就使用 sys.getdefaultencoding() 默認(rèn)編碼來對 unicode 進(jìn)行轉(zhuǎn)換成 str 后輸出。

if sys.stdout.encoding:
print unicode.encode(sys.stdout.encoding)
else:
print unicode.encode(sys.getdefaultencoding())

解決辦法

解決辦法一:
最不正確的解決方法:在頭部文件加上

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

這種方法通過 dirty hack 的方式在 Python 剛啟動時更改了 Python 的默認(rèn)編碼為 utf-8。此后:
print sys.getdefaultencoding()
utf-8
這個方法并不是真正地直接解決了問題。就如上述所說,Python 只是在sys.stdout.encoding 為 None 時才會使用默認(rèn)編碼來轉(zhuǎn)換需要 print 的 unicode 字符串。那萬一在sys.stdout.encoding 存在,但為 ascii 的情況下呢?這樣即使更改了 Python 的默認(rèn)編碼,同樣還是會出現(xiàn) UnicodeEncodeError 報(bào)錯。 所以對本問題來說,這個方法治標(biāo)不治本。

解決辦法二:
在 print 的時候顯式地用正確的編碼來對 unicode 類型的字符串進(jìn)行 encode('正確的編碼') 為 str 后, 再進(jìn)行輸出。
而在 print 的時候,這個正確的編碼一般就是 sys.stdout.encoding 的值。但也正如上述所說,這個值并不是一直是可靠的,因此需要根據(jù)所使用的平臺和控制臺環(huán)境來判斷出這個正確的編碼。

而在 Mac 下這個正確的編碼一般都是 utf-8,因此若不考慮跨環(huán)境的話,可以無腦地一直用 encode('utf-8') 和 decode('utf-8') 來進(jìn)行輸入輸出轉(zhuǎn)換。

解決辦法三:
雖然解決方法 2 是最正確的方式,但是有時候在 Sublime Text 里調(diào)試些小腳本,實(shí)在是懶得再在每個print 語句后面寫一個尾巴 .encode('utf-8')。那么有沒有辦法能讓 Sublime Text 像在終端里一樣直接就能 print u'中文' 呢?也就是說能不能解決 sys.stdin/stdout/stderr.encoding 為 None 的情況呢?

答案肯定是有的,一種方法是用類似更改默認(rèn)編碼的方法一樣,用 dirty hack 的方式在 Python 代碼中去顯式地更改 sys.stdin/stdout/stderr.encoding 的值。一樣是不推薦,我也沒嘗試過,在這里就不詳說了。
另一種方法則是通過設(shè)置 PYTHONIOENCODING 環(huán)境變量來強(qiáng)制要求 Python 設(shè)置 stdin/stdout/stderr 的編碼值為我們想要的,這是一個相對比較干凈的解決方法。
在 Mac 下對全局 GUI 程序設(shè)置環(huán)境變量的方法是:使用 launchctl setenv <<key> <value>, ...>命令對所有 launchd 啟動的未來子進(jìn)程設(shè)置環(huán)境變量。
而 Sublime Text 提供了一個設(shè)置 Build System 環(huán)境變量的方法,這個方法各平臺的 Sublime Text 都適用。

設(shè)置 Sublime Text 的 Python Build System 環(huán)境變量的步驟如下:

將 Sublime Text 默認(rèn)的 Python Build System 的配置文件 Python.sublime-build(找到這個文件的最好方法是安裝插件 PackageResourceViewer)復(fù)制一份到 Sublime Text 的 /Packages/User 文件夾下(在 Mac 和 Sublime Text 3 下這個路徑是 ~/Library/Application Support/Sublime Text 3/Packages/User)。
打開編輯新復(fù)制來的 Python.sublime-build 文件,如下加上一行設(shè)置 PYTHONIOENCODING 環(huán)境變量為 UTF-8 編碼的內(nèi)容,并保存:

{
    "shell_cmd": "python -u \"$file\"",
    "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
+   "env": {"PYTHONIOENCODING": "utf8"},
    "selector": "source.python"
}

這樣一來終于在這么長的文章后能在 Sublime Text 里直接運(yùn)行 print u'中文',而不用再出現(xiàn)萬惡的UnicodeEncodeError 了。
既然都研究到這了,不妨我們試試把 PYTHONIOENCODING 設(shè)置成其它編碼看看會出現(xiàn)什么情況,例如設(shè)置成簡體中文 Windows 的默認(rèn)編碼 cp936:"env": {"PYTHONIOENCODING": "cp936"}

import sys
print sys.stdout.encoding
print u'你好'

cp936
[Decode error - output not utf-8]
[Finished in 0.1s]
[Decode error - output not utf-8],這就是 Sublime Text 在 Windows 下可能會出現(xiàn)的問題。這是因?yàn)?Sublime Text 的 Build System 默認(rèn)是用 utf-8 編碼去解讀運(yùn)行的輸出的,而我們指定了讓 Python 用 cp936 編碼來生成 str 字符串進(jìn)行輸出,那么就會出現(xiàn) Sublime Text 無法識別輸出的情況了。
解決辦法之一就是同樣在 Python.sublime-build 文件里設(shè)置 "env": {"PYTHONIOENCODING": "utf8"}來使得輸出統(tǒng)一為 utf-8。

或者是更改 Sublime Text 的 Build System 所接受的輸出編碼,將其改為一致的 cp936 編碼,同樣也是更改 Python.sublime-build 文件,加入一行:

{
    "shell_cmd": "python -u \"$file\"",
    "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
+   "encoding": "cp936",
    "selector": "source.python"
}

這里要注意,"env": {"PYTHONIOENCODING": "cp936"}和"encoding": "cp936",是兩個不同的概念,PYTHONIOENCODING是表示讀取和輸出時進(jìn)行解碼編碼的格式。"encoding"表示的是,python的build system所接受的輸出編碼。
這里要注意,PYTHONIOENCODING和encoding要一致,這樣輸出控制臺才行。詳細(xì)的資料,參考這篇文章

【已解決】Python字符串處理出現(xiàn)錯誤:UnicodeDecodeError: ‘a(chǎn)scii’ codec can’t decode byte 0xe6 in position 0: ordinal not in range(128)

注意到錯誤提示中的“ordinal not in range(128)”,意思是,字符不在128范圍內(nèi),即說明不是普通的ASCII字符,超出處理能力了。所以感覺是str類型的變量,無法處理超過ASCII之外的字符。所以想到去將對應(yīng)原始字符轉(zhuǎn)換為unicode:
gVal[``'newPostPatStr'``] ``= unicode``(gVal[``'newPostPatStr'``]);
然后再去調(diào)用上面的replace,結(jié)果此句執(zhí)行結(jié)果,也出現(xiàn)和上面同樣的錯誤,無法轉(zhuǎn)換為unicode。
最后是通過,在最開始的時候,得到gVal[‘newPostPatStr’]的值之后,
調(diào)用unicode時候指定對應(yīng)的編碼:
gVal[``'newPostPatStr'``] ``= unicode``(gVal[``'newPostPatStr'``], ``"utf-8"``);

然后就可以強(qiáng)制轉(zhuǎn)換為unicode了,然后之后的字符串處理,就都是可以正常的了。

【總結(jié)】

此處是最開始獲得某字符串變量,沒有通過指定編碼為utf-8轉(zhuǎn)換為unicode,然后接下來的操作,比如replace替換,就都無法處理包含了utf-8的,超出了128 range的字符,才會報(bào)UnicodeDecodeError錯的。

所以,以后遇到UnicodeDecodeError方面的錯誤,那就先去看看,是不是由于沒有指定合適的編碼。如果指定了對應(yīng)的編碼后,字符串的一切操作(replace, re.sub等),一般來說,就都可以正常操作了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 字符集和編碼簡介 在編程中常常可以見到各種字符集和編碼,包括ASCII,MBCS,Unicode等字符集。確切的說...
    蘭山小亭閱讀 8,610評論 0 13
  • 什么是編碼 任何一種語言、文字、符號等等,計(jì)算都是將其以一種類似字典的形式存起來的,比如最早的計(jì)算機(jī)系統(tǒng)將英文文字...
    隨風(fēng)化作雨閱讀 1,576評論 1 2
  • 繼上一篇文章字符集和編碼詳解總結(jié)了常見字符編碼后,這篇文章會對python中常見的編碼問題進(jìn)行分析和總結(jié)。由于py...
    __七把刀__閱讀 2,912評論 0 6
  • 寫python的過程中經(jīng)常出現(xiàn)各種蛋疼的編碼問題,于是通過上網(wǎng)查資料,自己做實(shí)驗(yàn),想徹底搞清楚這個問題。 編碼和解...
    allen哦閱讀 530評論 0 1
  • 昨晚一肚酒,幸運(yùn)的是沒有把酒鬼的夲性暴露出來,想著自己還能喝幾杯,誰知光頭李的肚量比我還大,秦總那也不在話...
    靜夜思今閱讀 237評論 0 0