Python 2.x 里的編碼實(shí)在是一件令人煩躁的事情。不斷有初學(xué)者被此問(wèn)題搞得暈頭轉(zhuǎn)向。我自己也在很長(zhǎng)一段時(shí)間內(nèi)深受其害,直到現(xiàn)在也仍會(huì)在開(kāi)發(fā)中偶爾被坑。在本教室的提問(wèn)和討論中,編碼問(wèn)題也占據(jù)了相當(dāng)大的比重。 然而這個(gè)問(wèn)題并不能一兩句話輕易解答。今天在這里稍微分析一下,希望能幫各位理清這里面的問(wèn)題。 要弄清編碼問(wèn)題,首先明確幾個(gè)概念: str、unicode、encode、decode str 就是我們通常說(shuō)的字符串,在 python 中是由引號(hào)包圍的一串字符。但是 Python 中的默認(rèn)字符并不包括中文及其他復(fù)雜字符(其他非英語(yǔ)語(yǔ)言、特殊符號(hào)等)。雖然你可以定義"你好"這樣的字符串,但在 Python Shell 中輸入一下你就會(huì)發(fā)現(xiàn):
在程序中,這兩個(gè)字符是被其他的一些按照某種格式的普通字符所表示。進(jìn)一步地,輸入:
字符串的長(zhǎng)度也并不是想象中的2。 這就是我們一直說(shuō)的編碼。即通過(guò)某種規(guī)定的形式,用一些字符表示另一些字符。目的就是為了用少量的簡(jiǎn)單字符表示更多更復(fù)雜的字符。 上述的結(jié)果,是因?yàn)槲业?Python Shell 里默認(rèn)使用 UTF-8 對(duì)字符進(jìn)行了編碼。如果你在 Windows 下進(jìn)行嘗試,會(huì)是不一樣的結(jié)果,因?yàn)?Windows 默認(rèn)使用的是一種叫做 cp936 的編碼。 當(dāng)你需要通過(guò) Python 得到某些輸入或者輸出,比如讀取網(wǎng)頁(yè),輸出到控制臺(tái),讀寫(xiě)文件等等,需要處理的都是 str 類(lèi)型。所以必然逃不過(guò)編碼的問(wèn)題。 unicode 為了處理不同編碼的字符,于是有了 unicode。unicode 本身是一種編碼,因?yàn)樽銐虻拈L(zhǎng)度,它可以包容各種文字和符號(hào)。同時(shí)它也是 Python 中的一種類(lèi)型。在表示形式上,是字符串的引號(hào)前加上一個(gè) u。比如
可以看出,unicode 和 str 是兩種不同的類(lèi)型。 雖然 unicode 很強(qiáng)大,但在 Python 2 中,它不能被直接輸出,而必須通過(guò)某種編碼轉(zhuǎn)成 str。 encode & decode encode 是 unicode 的一種方法,作用是按照某種形式對(duì)其進(jìn)行編碼,轉(zhuǎn)為 str。如:
decode 是 str 的一種方法,作用是按照某種形式對(duì)其進(jìn)行解碼,轉(zhuǎn)為 unicode。如:
對(duì) unicode 進(jìn)行 encode 沒(méi)太大問(wèn)題,但對(duì) str decode 時(shí),因?yàn)?str 本身是有某種編碼的,這時(shí)候如果指定的編碼不符,就會(huì)產(chǎn)生討厭的亂碼:
在默認(rèn)的 utf-8 編碼環(huán)境下強(qiáng)行使用 cp936 編碼,就會(huì)出現(xiàn)亂碼。 小結(jié)一下就是: 輸入 -> str -> decode -> unicode -> encode -> str -> 輸出 那么通常問(wèn)題出在哪里呢? 一般情況下,我們?cè)诔绦蚶镒龅闹皇?/p> 輸入 -> str -> 邏輯處理 -> str -> 輸出 但在輸入輸出過(guò)程和中間的邏輯處理時(shí),很可能 Python 幫我們默認(rèn)做了一些 encode 和 decode 的工作。比如說(shuō),print 會(huì)按照環(huán)境的默認(rèn)形式進(jìn)行編碼,當(dāng)在需要 unicode 類(lèi)型的操作而程序拿到的是一個(gè) str 類(lèi)型是,會(huì)使用 ascii 進(jìn)行解碼。前者將可能導(dǎo)致亂碼顯示,而后者就直接報(bào)錯(cuò)。 舉兩個(gè)例子: 1 程序從網(wǎng)上抓取一段網(wǎng)頁(yè),中間的文字是 gbk 編碼,如 '\xbb\xb6\xd3\xad'(歡迎)。但抓取下來(lái),從默認(rèn) utf-8 控制臺(tái)輸出時(shí),就變成了 ???。同理存在于,Windows 下抓取了 utf-8 編碼的網(wǎng)頁(yè)。正確的處理方法是手動(dòng)做一次解碼:
2 程序有一段從輸入得到的 cp936 編碼文字,如 '\xd5\xc5\xc8\xfd'(張三),和從數(shù)據(jù)庫(kù)取出的 unicode 字符串,如 u'\u597d\u4eba'(好人),兩者需要做拼接時(shí):
拋出了經(jīng)常發(fā)生的 UnicodeDecodeError 異常。這是因?yàn)樵?str 和 unicode 做 + 操作時(shí),會(huì)自動(dòng)將 str 轉(zhuǎn)成 unicode,并且使用了 ascii 編碼。同樣的問(wèn)題也會(huì)發(fā)生在對(duì)一個(gè) str 對(duì)象直接使用 encode 的時(shí)候。比如:
原因也是一樣,encode 是 unicode 類(lèi)型的方法,對(duì) str 進(jìn)行調(diào)用時(shí),程序會(huì)默認(rèn)先直接試圖用 ascii 編碼把 str 轉(zhuǎn)成 unicode。 正確的做法:
另外還有個(gè)要注意的地方是,Python 代碼的 py 文件默認(rèn)是是用 ascii 編碼,所以在程序里有中文的時(shí)候,需要在文件開(kāi)頭指定編碼,例如:
有些 IDE 比如 PyScripter 會(huì)另外設(shè)置你的文件編碼,有時(shí)還會(huì)有沖突。 要注意搞清: 程序文件本身的編碼 - 你在程序里賦值的字符串 輸入來(lái)源的編碼 - 獲取的變量值 輸出環(huán)境的編碼 - 控制臺(tái)、文件、網(wǎng)頁(yè) 最好能保證這幾個(gè)的一致性,不一致時(shí)也要做好相應(yīng)的轉(zhuǎn)換,才能避免掉進(jìn)編碼的坑。 最后,為巴黎恐怖襲擊中的遇難者以及所有戰(zhàn)爭(zhēng)的受害者哀悼。本教室有不少身處國(guó)外的讀者,各位注意自身安全。愿大家都平安。 |
|
來(lái)自: 編程教室 > 《待分類(lèi)》