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

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

image

有個特別好玩的現象,當咱們爲了python編碼頭疼的時候,幾乎搜索到全部的文章都會先發一通牢騷。而後在迫不得已地寫解決思路(是解決思路不是方案)。這個問題真不是新手問題,即便是十幾年python老手也常常頭疼。中國外國都同樣。看看這個python專家在 PyCon大會上用半個多小時講解亂碼的視頻就瞭解了,他本身都給本身的來回encoding, decoding, encoding, decoding說暈了,臺下舉手他都拒絕回答,可想而知這個問題複雜性。

我認爲,幾乎每一個pythoner,都會有一段人生浪費在了編碼上。能夠說這個問題,是若是你不不折不扣解決,就永遠會崩潰的地步。翻看我曾經寫的數篇文章就知道了:html

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

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

若是python的print的特性都沒有了解的話,但願你不要貿然嘗試用print去調試測試亂碼編碼的問題。
這裏的print厲害到讓你不高興的地步——它無論你塞過來的是什麼格式什麼編碼,字符串數組對象什麼的的都一口氣全打印出來。
感受好像很好,但實際上是咱們仔細研究編碼問題的最大阻礙。
由於你塞給print一個unicode它能打出中文,塞一個utf-8或iso8895給它,也同樣給你打印出原文。這樣以來,你看着它出現原文後,就欣喜若狂產生了一種勝利的錯覺。
因此我想在這裏最早說清楚它:git

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

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

看出區別了嗎?明確了這點,再來繼續研究編碼問題。web

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

unicodestr

若是type(字符串)顯示結果是str,其實指的是bytes字節碼。
而其它各類咱們所說的utf-8gb2312等等也都是Unicode的不一樣實現方式。
這裏不要去考慮那麼複雜,只要先記住這兩大陣營就行。json

encodingdecoding

絕對要記住的:
unicode轉換到str,這個叫encoding,編碼。
str轉換到unicode,這個叫decoding,解碼。
image
(圖片引用自知乎相關某答案。)數組

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

而後來看個案例。
image測試

經過上面兩種格式的對比咱們看到,str和unicode的各類區別。

那麼,既然變量裏面會出現兩種不一樣的格式,若是咱們把兩種格式的字符串連在一塊兒操做會發生什麼呢?
以下:
image編碼

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

以上是咱們用顯性字符串來比較兩種格式字符串的區別。

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

本地文件編碼測試

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

而後咱們來讀取一下:

image

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

image

這種時候其實是最迷糊也最容易形成以後錯誤的,就是分不清該編碼仍是該解碼。

因此上面提到,必需要記住這兩個區別。
那麼若是如今我搞反了怎麼辦?就會再次出現下面錯誤:

image

話說回來,咱們該怎麼統一他們呢?

爲了不兩種格式的字符串在一塊兒亂搞,統一他們是必須的。可是以哪種爲統一的呢,unicode仍是bytes?

網上各類文章統一口徑,要求代碼中出現全部的變量都統一爲unicode。
但是我在實踐和測試中都愈來愈發現:這種作法真的不那麼可靠,甚至我懷疑有可能咱們碰到那麼多的問題,都是由它攪亂引發的。

下面咱們來看看作經常使用的環境下字符串都是什麼格式

image

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

實際上,requests返回的response中, 除了用.text獲取內容,咱們還能夠用.content獲取一樣的內容,只不過是bytes格式。

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

爲何咱們不能把全部字符串變量統一爲unicode呢?

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

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

這裏由於不知道來源的編碼,因此必須用unicode()來解碼,而不能用.decode('utf-8'),由於顯然你不能亂寫解碼名稱,若是來源果然是(很大概率是)ISO8859等方式,那麼錯誤的解碼確定會產生亂碼,或者直接程序報錯。切記!

因此這裏只能用unicode()解碼。以下例:

image

結論:必定記住,全文都統一用str格式字符串

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

只要控制好外來源的字符串,統一爲str,其它一切都好說!

實際上,我發現遇到的絕大多數編碼問題,實際上不是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的亂碼問題,又是一個新的較長篇章。我會單分一篇,請到個人專欄裏找。
相關文章
相關標籤/搜索