python編碼問題

幾個(gè)基本概念

bit
二進(jìn)制位, 是計(jì)算機(jī)內(nèi)部數(shù)據(jù)儲(chǔ)存的最小單位,11010100是一個(gè)8位二進(jìn)制數(shù)。一個(gè)二進(jìn)制位只可以表示0和1兩種狀態(tài)(21);兩個(gè)二進(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é)為單位存儲(chǔ)和解釋信息,規(guī)定一個(gè)字節(jié)由八個(gè)二進(jìn)制位構(gòu)成,即1個(gè)字節(jié)等于8個(gè)比特(1Byte=8bit)。八位二進(jìn)制數(shù)最小為00000000,最大為11111111;通常1個(gè)字節(jié)可以存入一個(gè)ASCII碼,2個(gè)字節(jié)可以存放一個(gè)漢字國標(biāo)碼。


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

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

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

常見的字符編碼

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

編解碼過程

字符串在Python內(nèi)部的表示是unicode編碼,因此,在做編碼轉(zhuǎn)換時(shí),通常需要以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)碼的時(shí)候一定要先搞明白,字符串str是什么編碼,然后decode成unicode,然后再encode成其他編碼

整個(gè)過程如下,即
? decode ???? encode
str ---------> unicode --------->str

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

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

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

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

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

f = open('test.txt','r')
s = f.read() #讀取文件內(nèi)容,如果是不識(shí)別的encoding格式(識(shí)別的encoding類型跟使用的系統(tǒng)有關(guān)),這里將讀取失敗
'''假設(shè)文件保存時(shí)以gb2312編碼保存'''
u = s.decode('gb2312') #以文件保存格式對(duì)內(nèi)容進(jìn)行解碼,獲得unicode字符串
'''下面我們就可以對(duì)內(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給我們提供了一個(gè)包c(diǎn)odecs進(jìn)行文件的讀取,這個(gè)包中的open()函數(shù)可以指定編碼的類型:

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

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

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

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

  • 如何獲得系統(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)亂碼,甚至錯(cuò)誤,其實(shí)是由于IDE的結(jié)果輸出控制臺(tái)自身不能顯示字符串的編碼,而不是程序本身的問題。

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

s=u"中文"
print s

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

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

分析

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

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

import sys
print sys.getdefaultencoding()

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

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

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

是的,其實(shí) Python 在 print unicode 時(shí)真正涉及到的是另一組編碼:stdin/stdout/stderr 的編碼,也就是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出的編碼??梢酝ㄟ^以下命令來查看,這里是在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 來對(duì) unicode 進(jìn)行轉(zhuǎn)換了。這樣就出現(xiàn)了本文開頭所說的那個(gè) UnicodeEncodeError 問題。
在mac下他的這三種輸出都是utf-8,實(shí)際上輸出等于print u'中文'.encode('UTF-8'),所以輸出正常。

python2 向控制臺(tái)print輸出是流程

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

Python 啟動(dòng)時(shí),當(dāng)它發(fā)現(xiàn)當(dāng)前的輸出是連接到控制臺(tái)的時(shí)候,它會(huì)根據(jù)一些環(huán)境變量,例如環(huán)境變量LC_CTYPE,來設(shè)法判斷出 sys.stdin/stdout/stderr.encoding 編碼值。
當(dāng) Python 無法判斷出所需的編碼時(shí),它會(huì)將 sys.stdin/stdout/stderr.encoding 的值設(shè)置為None。
print 時(shí)判斷字符串是否是 unicode 類型。
如果是的話,并且 sys.stdout.encoding 不為 None 時(shí),就使用 sys.stdout.encoding 編碼對(duì) unicode 編碼成 str 后輸出。
如果 sys.stdout.encoding 為 None 的話,就使用 sys.getdefaultencoding() 默認(rèn)編碼來對(duì) 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 剛啟動(dòng)時(shí)更改了 Python 的默認(rèn)編碼為 utf-8。此后:
print sys.getdefaultencoding()
utf-8
這個(gè)方法并不是真正地直接解決了問題。就如上述所說,Python 只是在sys.stdout.encoding 為 None 時(shí)才會(huì)使用默認(rèn)編碼來轉(zhuǎn)換需要 print 的 unicode 字符串。那萬一在sys.stdout.encoding 存在,但為 ascii 的情況下呢?這樣即使更改了 Python 的默認(rèn)編碼,同樣還是會(huì)出現(xiàn) UnicodeEncodeError 報(bào)錯(cuò)。 所以對(duì)本問題來說,這個(gè)方法治標(biāo)不治本。

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

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

解決辦法三:
雖然解決方法 2 是最正確的方式,但是有時(shí)候在 Sublime Text 里調(diào)試些小腳本,實(shí)在是懶得再在每個(gè)print 語句后面寫一個(gè)尾巴 .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 的編碼值為我們想要的,這是一個(gè)相對(duì)比較干凈的解決方法。
在 Mac 下對(duì)全局 GUI 程序設(shè)置環(huán)境變量的方法是:使用 launchctl setenv <<key> <value>, ...>命令對(duì)所有 launchd 啟動(dòng)的未來子進(jìn)程設(shè)置環(huán)境變量。
而 Sublime Text 提供了一個(gè)設(shè)置 Build System 環(huán)境變量的方法,這個(gè)方法各平臺(tái)的 Sublime Text 都適用。

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

將 Sublime Text 默認(rèn)的 Python Build System 的配置文件 Python.sublime-build(找到這個(gè)文件的最好方法是安裝插件 PackageResourceViewer)復(fù)制一份到 Sublime Text 的 /Packages/User 文件夾下(在 Mac 和 Sublime Text 3 下這個(gè)路徑是 ~/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 了。
既然都研究到這了,不妨我們?cè)囋嚢?PYTHONIOENCODING 設(shè)置成其它編碼看看會(huì)出現(xiàn)什么情況,例如設(shè)置成簡(jiǎn)體中文 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 下可能會(huì)出現(xiàn)的問題。這是因?yàn)?Sublime Text 的 Build System 默認(rèn)是用 utf-8 編碼去解讀運(yùn)行的輸出的,而我們指定了讓 Python 用 cp936 編碼來生成 str 字符串進(jìn)行輸出,那么就會(huì)出現(xiàn) Sublime Text 無法識(shí)別輸出的情況了。
解決辦法之一就是同樣在 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",是兩個(gè)不同的概念,PYTHONIOENCODING是表示讀取和輸出時(shí)進(jìn)行解碼編碼的格式。"encoding"表示的是,python的build system所接受的輸出編碼。
這里要注意,PYTHONIOENCODING和encoding要一致,這樣輸出控制臺(tái)才行。詳細(xì)的資料,參考這篇文章

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容