原文見python編碼的意義,感謝jiminhuang大神
編碼,還是編碼
python2的直鉤——編碼異常
當你用python打開一篇中文文檔,準備讀取里面的數據開始實驗...當你處理好你的數據,打算打印出易于閱讀的結果給boss檢查...甚至當你剛剛開始編寫自己的代碼,就寫了一句話...
text ='什么鬼'
只要你開始運行自己的代碼,信心滿滿期待搞定回寢時
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
以及下面這樣的悲劇
SyntaxError: Non-ASCII character '\xe5' in file test.py on line 3, but no encoding declared; seehttp://www.python.org/peps/pep-0263.htmlfor details
于是你10點前回到寢室以及之后的一系列計劃全部泡湯了,垂頭喪氣的坐下來,你看到兩個動詞格外亮眼——decode,encode。而他們的中文釋義,就是python2對新手的最大陷阱——編碼。
當我們談論編碼時我們在談論什么
python中有關編碼問題的對象有basestring,str,unicode, 標準庫有codecs等,在這篇文章里我們基本上不會提到標準庫,而僅僅簡單的對對象們進行分析。因為這就夠了!
事實上,我們常犯的編碼問題,從拋出異常的角度來說分為兩種,很明顯,本文一開頭也列出這兩種異常的打印情形,它們分別是
- py文件編譯未指定文件字符集導致的解碼異常
- 我
- 我
- 我
- 字符串對象互相轉換時使用默認編碼導致的異常
之后將會分別對兩類異常的處理方法做說明。實際上, 第一類錯誤本質上則是 python 自己運行時打開文件進行解碼造成的異常。
第二類錯誤,我們所犯的解碼異常,就是字符串對象互相轉化時沒有指定字符編碼
黃金原則
本文章之所以比其他寫編碼的文章稍微多一點價值的原因,在于本文在這里——第一章的最后一小節——就用最大的字體寫了處理這類異常的黃金原則
不要驚慌
以及在此之下的,你真正可以掌握的,避免這類異常的黃金原則
只有在IO的時候,才進行轉換
這意味著
- 因為某些原因, python 打開流讀取出的是str,所以用你知道的某一種編碼把它解碼成unicode
- 大概是因為同樣的原因,python 的輸出也是str, 但是任何一個unicode只有到要輸出的時候才編碼成str
- 在此之間,放棄該死的str,忘了它,當你開始處理的時候,確保你的每一個字符串對象都是unicode
- sfs f
- sfdsf
掌握了以上原則,會避免99%的編碼異常發生。當然,正在閱讀這篇文章的人中有80%肯定犯過了1000次以上這種錯誤,為了避免剩下1%的發生。
還有20%的人剛開始準備寫python,他們會在看完這篇文章后100%會犯錯誤,本文的作者正在和80%的人一起微笑著等他們第二遍來看這篇文章。
順便說一下,這篇文章到這里主要內容就結束了,如果你想找到解決方法和原因,上面已經說的清清楚楚了,接下來主要是各種重復和閑談,幫助你了解這之后的內幕,不過第二次來看的同學們記得往下看哦。
第一類異常
一點小trick
第一類異常是python 自己打開你寫的源文件時拋出的解碼異常,這句話被說了兩遍說明它一定-----------------------------很不重要,不過你也可以當做一個冷知識儲備一下。
SyntaxError: Non-ASCII character '\xe5' in file test.py on line 3, but no encoding declared; seehttp://www.python.org/peps/pep-0263.htmlfor details
所有的這類異常都是因為你在源文件寫代碼時中直接使用了國際化文本——也就是你沒有辦法在ascii碼表里找到的字符。同時你“聰明”的沒有做下面說的這一件事
在文件的開頭使用注釋聲明文件編碼
# coding:文件編碼```
####pep263
如果你有審慎的閱讀出錯信息,你一定會注意到一個網址出現在其中。沒錯,那就是python社區的技術提案**PEP**(*python enhancement proposals*), 涵蓋了從版本更新特性至python格式指南的一切東西,如果你有一個昏昏欲睡的下午的話,可以浪費一點時間看看它。
在[pep263](http://www.python.org/peps/pep-0263.html)里,詳細的介紹了某種異常發生的原因,以及它提出的一種聲明注釋的解決方案。接下來我們簡要介紹的一些內容你都可以在上面找到,當然它是英文的
####原因
自從pep263成為python標準后,python的編譯器或者說是編碼器在開始解釋前,先要經過以下幾個步驟:
1. 讀出文件內容
2. 將內容根據**文件編碼**解碼成為unicode
3. 分詞標注
4. 解釋它,并把每一個直接寫出的unicode(比如:`u'什么鬼'`)創建一個unicode對象,對str對象,將會從unicode按照**文件編碼**再編碼成為str對象
異常原因在于,python的*_默認文件編碼_*,不是utf-8,不是gbk,而是**ascii**。
####快出來看上帝
>他們彼此商量說,來吧,我們要作磚,把磚燒透了。他們就拿磚當石頭,又拿石漆當灰泥。
他們說,來吧,我們要建造一座城和一座塔,塔頂通天,為要傳揚我們的名,
免得我們分散在全地上。耶和華降臨,要看看世人所建造的城和塔。
耶和華說,看哪,他們成為一樣的人民,都是一樣的言語,
如今既作起這事來,以后他們所要作的事就沒有不成就的了。
我們下去,在那里變亂他們的口音,使他們的言語彼此不通。
于是,耶和華使他們從那里分散在全地上。
他們就停工,不造那城了!!!
本文作者之所以在這里引用一段舊約(某知道里的答案),完全是因為作者想展示一下自己的逼格。
事實上,本章關于第一類異常的處理在第一小節就已經結束了,后面完全是雜談,但其實也許是很重要的。
上帝機智的攪亂了人類的語言的1000年后,本文作者覺得可能是上帝的第二次降臨,人類中最聰明的一群人,也許也是最蠢的,程序員,開始想要在自己的處理對象里增加字符了。
考慮到轉換的問題,很容易就想到,如果把每一個字母,每一個標點,每一個符號與計算機中特殊的一位一一對應的話,就能夠實現對字符的處理了。
那么,這里假設你已經有一定的計算機底層知識了,這樣一個唯一的對應的編碼至少需要多少位?
這里提供一些數據, 所有大小寫字母一共52個,0~9數字需要10個,加上逗號,句號,感嘆號...
答案是**7**。
而**ascii**碼,也就是美國信息交換標準碼(*American Standard Code for Information Interchange*),1967年發布,7位字符編碼中影響最大的一種。二進制取值范圍0000000~1111111,十六進制表示00h~7fh
事實上當時**ascii**碼主要是用于電傳打字機的,但是現在已經基本上一統計算機的天下。但它的問題同樣很嚴重,就在它的名字里,它實在太美國化了。阿拉伯文,日語,當然還有我們的中文,通通找不到自己的位置,于是出現無窮多種擴展ascii編碼,它們的前7fh的編碼與ascii保持一致,而使用自己的擴展位實現對其他語言及符號的編碼
我們統稱這一類為**ANSI**編碼標準,在這里各國的程序員們就開始各自發揮了:
>- gb大家族,我朝官方認證出品的一系列字符集
- latin大家族,主要是對拉丁字母及西歐一些國家的字母編碼
- Big 5,呆灣主要使用的針對繁體中文的編碼
...
你可以想象這是有多么混亂,實際上都不用想象,現在還有無數人在求助,我的文檔打開亂碼怎么辦?
因此,**Unicode**響應時代的號召,橫空出世。**Unicode**使用16位編碼,編碼范圍0000h~ffffh,它對還在捉對廝殺的各國程序員說,別打了,我們一個字符集包括世界上所有字符就好啦
但是,**Unicode**只是給定了字符與編碼的對應關系,它的實現方式還是有很多種,其中就有**UTF**大家族(其實是美帝的程序員發現它們要為一輩子都可能見不到的中文,把英文編碼提高一個字節時,wtf!)
于是就有了**UTF-8**,使用一個字節表示英文,而三個字節表示中文的編碼方式??!
####注釋聲明
在一大段閑談之后,我們簡單的說明了各大字符集的由來,所以,現在問題來了,面對各國程序員的各種編碼的文件,一門編程語言應該如何處理呢?
對于python,它的默認文件編碼是ascii碼,在遇到國際化文本,也就是其他編碼字符集時,就會無法編碼(老天,這個編碼都超過ffh了!)
因此,呼應文章開頭,[pep263](http://www.python.org/peps/pep-0263.html)指出,python的程序員們都應該在文件的開頭寫上文件的默認編碼,同時一個文件只能有一種編碼!也就是:
coding:utf-8
至于為什么與你平常所見到的模式:
-- coding: utf-8 --
不一樣,本文作者會輕易告訴你``-*-``是裝飾用的嗎
###第二類異常
***
Unicode會夢見小綿羊嗎?
在python中,其實是python2中,與其他語言不同的是,有兩個經常被用來實際操作的字符串對象
- str
- Unicode
要說明兩者之間的關系,實在不是一個很難的問題。我們可以非常非常非?!菀椎牡玫綄ο蟮睦^承關系,如下圖:
object
basestring
str
unicode
可以看到,unicode對象與str對象都繼承自basestring。basestring是一個抽象類,字符串及其操作由子類str及unicode各自實現。所以
**基本上所有str能進行的操作unicode都能進行**。
####編碼與解碼
在python中,我們所說的編碼**encode**,特指從unicode轉換成指定編碼的str對象
>str = unicode.encode(字符編碼)
而所說的解碼**decode**,特指從指定編碼的str對象轉換為unicode對象
> unicode = str.decode(字符編碼)
如果你有好好的閱讀**來看上帝把**那一節,就很容易理解這二者的轉換,相當于我們把不同字符集中對字符的編碼與Unicode全世界統一的編碼互相轉換。
而python2最大的直鉤也在于此,它的默認編碼是**ascii**。
####然而ascii早已看穿了一切
我們之所以要重復提ascii,是因為它真的很重要!理解它是python2默認編碼將會讓你真正理解第二類異常的原因:
> 進行編碼解碼時沒有指定字符集編碼,python默認使用ascii進行編碼解碼
因為ascii僅包含英文大小寫及幾十個常用符號,因此,當你的編碼解碼的對象里包含中文或者其他亂七八糟東西的時候
> **UnicodeDecodeError**: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
####Do you know your object?
在這一節,我們將會談到何時會觸發第二類異常,也就是所謂的情景檢查。事實上,在本文作者看來,所有的第二類異常都在一種情形下發生:
> 程序員混用了unicode與str對象
一旦開始錯誤的使用unicode或者str,都將很有可能導致第二類異常。然而,遺憾的是,直到它拋出了異常,大部分沒讀過這篇文章的人依然沒有意識到問題在哪里。其主要原因在于:
1. str對象支持的方法與unicode基本完全一樣
2. str與unicode都是繼承自basestring,大部分對字符串操作的方法只會檢查是不是basestring類及其子類
3. 任何一個類都來自object(這里指新類),都默認包含內建方法__str__,該方法用于將實例轉換成str對象,換言之,你能夠print任何一個對象,都因為默認使用內建方法轉換了。
4. 各個類都可以改寫這個內建方法,而unicode改寫為使用默認編碼解碼
這就使得一個初學者的程序中,字符串對象既有unicode,也有str,而他完全沒有意識到,當然也是由于大部分市面上的書在這一點上都及其不負責任。想象一下,當你以為自己的對象a是一個str,而實際上是一個unicode,**你想當然的進行print輸出時,就會默認調用unicode的__str__進行轉化輸出,在這里進行了默認編碼ascii的解碼**,error!同理適用于當你把一個str當unicode用的時候
> 一旦你開始混用兩種對象,在你不注意的地方,就會發生默認編碼解碼!
另外一種稍微可以諒解的情況是,python2關于文件流的封裝實在太過坑爹,基本上所有文件流最終返回和寫入的都是str對象。簡單的舉個例子,你打開一個文件,按行讀取的每一行,都是一個str對象!那些只告訴你這樣可以讀,不告訴你返回類型(雖然寫了你也不大可能注意)的技術博客都是在耍流氓!
```python
with open('data.txt', 'r') as f:
for line in f: #line 是一個str對象!
所以在看到這里的時候,請務必檢查你的程序,檢查你的每一個字符串對象,確定它是你想要的類型,要知道,我們所接觸的大部分數據都會有中文,千萬不要等到報錯了才開始糾錯
Do you know your object?
No?。?br> Go to know your object!
放過str,請找unicode
為什么我們要放棄str?
簡單的理由,str不僅需要我們知道它的編碼,還需要根據輸出編碼做轉換。假設你有一個utf8編碼的str對象,想要輸出到gbk編碼的控制臺上,你要這么做:
- utf8解碼成unicode
- unicode編碼成gbk
為什么我們不從一開始對象處理的時候就用unicode!
粗暴的理由,python3里面已經沒有str這種東西了!
請記住黃金原則
只有在IO的時候,才進行轉換
這意味著
- 因為某些原因, python 打開流讀取出的是str,所以用你知道的每一種編碼把它解碼成unicode
- 大概是因為同樣的原因,python 的輸出也是str, 但是任何一個unicode只有到要輸出的時候才編碼成str
- 在此之間,放棄該死的str,忘了它,當你開始處理的時候,確保你的每一個字符串對象都是unicode
是不是在哪里看到過? 不要在意這些細節~
按照黃金原則編寫能確保你的每一個進行處理的字符串對象都是unicode,同時只在io處進行轉換確保你只有在這個時候才需要考慮編碼的問題,也符合面向對象封裝的概念,也是最pythonic的做法
如果你還不知道什么是pythonic,請直接運行以下python代碼
import this
上面所說的是最正確的解決方法,當然有同學就會問啦,下面這種為什么不是最正確的呢?
import sys
sys.setdefaultencoding(字符編碼)
這種方法是在飲鴆止渴,完全沒有解決你的實際代碼問題。它只是將python默認編碼替換成了你想要的編碼(utf-8之類),一旦有新的編碼類型的str對象出現,你的程序就會重新開始報錯。所以不推薦這種方法,它會掩蓋掉你程序的大部分問題。
異常蛋疼的windows控制臺
簡單粗暴
就在不久前,本文作者在服務器上部署爬蟲代碼,就不得不在控制臺輸出(當然不是因為作者懶得用其他方式跑代碼),結果是一連串的亂碼,自認不是新手的作者完全不能忍了,于是心平氣和的坐下來研究了下windows控制臺的編碼
事實上,windows的控制臺的字符集編碼不叫字符集編碼,而叫代碼頁,多么古怪的名字!于是我們很直接的查到了utf-8的代碼頁是65001
然后再輸出的時候發現,每log一行就在報一行的error,看輸出信息是log的流往控制臺寫的時候報的錯,不過既然能打印出log,本文作者決定忽略掉那些error
所以
- 把代碼頁設置為65001chcp 65001
- 如果打印出了log,忽略那些錯誤把~
本小節是真的沒有查資料,如有錯誤和更好的解決方法,請不吝指正