關于解決Python亂碼問題的終極解決方案 (TL;DR)

關于解決Python亂碼問題的終極解決方案 (TL;DR)

image

有個特別好玩的現象,當我們?yōu)榱藀ython編碼頭疼的時候,幾乎搜索到所有的文章都會先發(fā)一通牢騷。然后在無可奈何地寫解決思路(是解決思路不是方案)。這個問題真不是新手問題,即使是十幾年python老手也經常頭疼。中國外國都一樣??纯催@個python專家在PyCon大會上用半個多小時講解亂碼的視頻就了解了,他自己都給自己的來回encoding, decoding, encoding, decoding說暈了,臺下舉手他都拒絕回答,可想而知這個問題復雜性。

我認為,幾乎每個pythoner,都會有一段人生浪費在了編碼上??梢哉f這個問題,是如果你不徹徹底底解決,就永遠會崩潰的地步。翻看我曾經寫的數篇文章就知道了:

牢騷結束,下面是我又一次用了兩個整天才測試整理書寫完成的ipython notebook筆記。ipynb格式的筆記源文件在這里,當然有可能會鏈接失效,有喜歡ipython的live coding筆記的且想要用這個筆記測試編碼的,請聯系我。

首先,需要先要了解python的print大法

如果python的print的特性都沒有了解的話,希望你不要貿然嘗試用print去調試測試亂碼編碼的問題。
這里的print厲害到讓你不高興的地步——它不管你塞過來的是什么格式什么編碼,字符串數組對象什么的的都一口氣全打印出來。
感覺好像很好,但其實是我們仔細研究編碼問題的最大阻礙。
因為你塞給print一個unicode它能打出中文,塞一個utf-8或iso8895給它,也一樣給你打印出原文。這樣以來,你看著它出現原文后,就欣喜若狂產生了一種勝利的錯覺。
所以我想在這里最先說清楚它:

不要輕易在研究亂碼的時候用print測試目標!

也不是說這種時候一點都不能用,而是說你可以print別的什么東西,但是如果想看清某個變量本質的話,千萬不要用。
這個時候要用print repr(字符串) ,或者最好是在命令行或ipython里面測試,像這樣:

image

看出區(qū)別了嗎?明確了這點,再來繼續(xù)研究編碼問題。

簡單來說,先要記住,在Python2里字符串只有兩大陣營:

unicodestr

如果type(字符串)顯示結果是str,其實指的是bytes字節(jié)碼。
而其它各種我們所說的utf-8,gb2312等等也都是Unicode的不同實現方式。
這里不要去考慮那么復雜,只要先記住這兩大陣營就行。

encodingdecoding

絕對要記住的:
unicode轉換到str,這個叫encoding,編碼。
str轉換到unicode,這個叫decoding,解碼。

image

(圖片引用自知乎相關某答案。)

來回記住這個問題,才能進入下一步!

然后來看個案例。


image

通過上面兩種格式的對比我們看到,str和unicode的各種區(qū)別。

那么,既然變量里面會出現兩種不同的格式,如果我們把兩種格式的字符串連在一起操作會發(fā)生什么呢?
如下:


image

看!著名的編碼錯誤UnicodeDecodeError: 'ascii' codec can't decode就這樣出現了!

以上是我們用顯性字符串來比較兩種格式字符串的區(qū)別。

但是,我們經常性處理python編碼問題,都不是在這種顯性的字符串上出現的,不是從網上爬取的就是從本地文件讀取的,意思就是文件內容龐大,編碼格式很難猜到是什么。
所以這里我們將問題再拆分為兩部分討論:本地文件和網絡資源。

本地文件編碼測試

首先在本地建立一個有中文的以utf-8格式保存的文本文件(實際上無論.txt還是.md等都無所謂,內容是一樣的)。
內容只有'你好'。

然后我們來讀取一下:

image

上面看到,從文件讀取出來的,就是str格式的字符串。
那么如果要把str轉化為unicode,就要解碼,也就是decoding.

image

這種時候實際上是最迷糊也最容易造成之后錯誤的,就是分不清該編碼還是該解碼。

所以上面提到,必須要記住這兩個區(qū)別。
那么如果現在我搞反了怎么辦?就會再次出現下面錯誤:

image

話說回來,我們該怎么統(tǒng)一他們呢?

為了避免兩種格式的字符串在一起亂搞,統(tǒng)一他們是必須的。但是以哪一種為統(tǒng)一的呢,unicode還是bytes?

網上各種文章統(tǒng)一口徑,要求代碼中出現所有的變量都統(tǒng)一為unicode。
可是我在實踐和測試中都越來越發(fā)現:這種做法真的不那么可靠,甚至我懷疑有可能我們碰到那么多的問題,都是由它攪亂引起的。

下面我們來看看做常用的環(huán)境下字符串都是什么格式

image

這樣就明白了:除了r.text返回的內容外,其它幾乎都是使用str格式,也就是bytes字節(jié)碼碼。所以我們只要轉化requests相關的內容就行!

實際上,requests返回的response中, 除了用.text獲取內容,我們還可以用.content獲取同樣的內容,只不過是bytes格式。

那就正和我們意,不用再去轉化每一個地方的字符串,而只要盯緊這一個地方就足夠了。

為什么我們不能把所有字符串變量統(tǒng)一為unicode呢?

先提醒下,變成unicode的過程,叫decoding。不要記錯。
.text經常把ISO8859等猜不到也檢測不到編碼(機率很低)的字符串扔過來,如果遇到的話,是很麻煩的。
decoding有兩種方法:

unicode(b'你好‘)
b'你好'.decode('utf-8')

這里因為不知道來源的編碼,所以必須用unicode()來解碼,而不能用.decode('utf-8'),因為顯然你不能亂寫解碼名稱,如果來源果真是(很大幾率是)ISO8859等方式,那么錯誤的解碼肯定會產生亂碼,或者直接程序報錯。切記!

所以這里只能用unicode()解碼。如下例:

image

結論:一定記住,全文都統(tǒng)一用str格式字符串

只要盯緊requests、json等這種經常處理外來資源的庫就好了。

只要控制好外來源的字符串,統(tǒng)一為str,其它一切都好說!

實際上,我發(fā)現遇到的絕大多數編碼問題,實際上不是python原生方法導致的,而是這些外來庫所引起的!因為每個模塊都會有自己的一套處理編碼的方式,你還真不知道它是采用哪個。就像JSON的dumps()一樣埋著大坑等著我們。所以真正應該盯緊的就是這些庫了。

下面是一個從獲取網絡資源(含中文且被requests認為編碼是ISO8850的網頁)到本地操作且存儲到本地文件的完整測試。

import requests

r = requests.get('http://pycoders-weekly-chinese.readthedocs.io/en/latest/issue5/unipain.html')

# write a webpage to local file
with open('test.html', 'w') as f:
    f.write( r.content )

# read from a local html file
with open('test.html', 'r') as f:
    ss = f.read()

大功告成!效果如下:

image

再也不用糾結、檢查每一個變量、寫一大堆嵌套轉化方法了!注意,只要盯緊各種外來模塊和庫的文字處理就夠了。

另外,關于JSON的亂碼問題,又是一個新的較長篇章。我會單分一篇,請到我的專欄里找。

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

友情鏈接更多精彩內容