本文轉自:http://cheng.logdown.com/posts/2015/01/15/ascii-codec-cant-encode-decode-errors-in-python
作者: CHENG
注意: 本文加入了個人的一些理解,由於水平有限可能會有部分錯誤,歡迎指正。另由於本文僅僅作為個人mark,排版也不美觀,所以建議到原文查看!
准備知識:
1 字符集,字符集實際上就是字符與數字之間的相互映射, 比如unicode字符集 ASCII字符集 GB2312字符集等等,
2 字符編碼,僅僅有字符集是不夠的,因為實際工作中我們需要將某些字保存在計算機上,計算機上保存的數據只能是0以及1的排列,還是拿字符集unicode來說,如果我們要將字符保存在計算機上那么就必須有某種規則將unicode字符轉換為0以及1,同時這種規則還能讓計算機區分出相鄰字符的分隔位置,utf32就是一種規則,簡單粗暴的用4個字節表示一個字符,以為unicode中最大的數遠遠小於2的32次方,所以這個規則保證了轉碼的可靠性,但是對於那些只使用英文的國家來說一個字符就要4字節有點浪費空間啊有木有!於是出現了utf8,可變長的且適用於unicode的一種編碼方式,這種方式有效的降低了空間浪費。
用Python 2.x會經常碰到一個錯誤:
UnicodeEncodeError: 'ascii' codec can't encode character ... : ordinal not in range(128)
搞清這個問題之前要先理解三個知識點:
- UTF-8 vs Unicode
- Encoding vs Decoding
- Python 2.7里的 str 和 unicode
1. UTF-8 vs Unicode
這一點已經在[之前的博文]里解釋過了(http://cheng.logdown.com/posts/2015/01/14/utf-8-vs-unicode:),這里我來總結一下
- Unicode 只是一個文字與數字之間的映射。比如,'漢' 這個字在Unicode里的代碼是 ‘6C49’,想對應的數字就是 27721。地球上每種語言里的每一個文字都有這樣一個相對應的數字標識。這個文字與數字的映射表就是 Unicode。
- 當我們把這個映射后的數字存儲在計算機上時,需要把它轉換成 0 和 1. 我們可以簡單的把 27721 轉換成二進制代碼 ’01101100 01001001‘ 來存儲。但問題是計算機怎么能夠知道這個兩個字節是代表一個文字而不是兩個文字? 這個時候就需要再有一種編碼形式來告訴計算機將這個字節視為一個文字。這個編碼就是UTF-8 (當然,UTF-8只是眾多編碼中的一種)
可以用這個順序來理解:
屏幕上看到的文字 Unicode代碼 根據UTF-8規范存在計算機內存或者硬盤里的模樣
漢 -> 6C49 -> 11100110 10110001 10001001
2. Encoding vs Decoding
在Python2中把一個Unicode類轉化為 0 和 1 的過程叫做Encoding。 把 0 和 1 反轉為Unicode類的過程叫做Decoding。Python3已經沒有unicode和str的區別了,取而代之的是byte和str
在Python 2.7版本里,ASCII是默認的Encoding和Decoding的方法。
3. Python 2.7里的 str 和 unicode
當你把一個字符(不管該字符是英文字母還是ASCII里不能包含的字符)賦值給一個變量時:
han = '漢'
這個變量的類型都會是str:
In [113]:han = '漢' type(han) Out[113]:str
但這里有很重要的一點需要理解:
In [124]:han = '漢' bin_han = '\xe6\xb1\x89' han == bin_han # 雖然在界面里我們看到的是'漢'這個字,但其實它是一堆字符,並不是我們看到的文字 Out[124]:True
str這個類的本質其實就是原始字符數據(raw byte data)。它並不是我們所看到的'漢'!
那么Unicode類也是這樣嗎?
In [114]:uni_han= u'漢' type(uni_han) Out[114]:unicode In [131]:uni_han= u'漢' u_han = u'\u6c49' uni_han == u_han # 在Unicode中存儲的是u'\u6c49'而不是你所看到的u'漢' Out[131]:True
理解了以上三個知識點,我們就可以很容易的解釋 'ascii' codec can't encode character 這個錯誤的緣由了。
用示例來解釋 'ascii' codec can't encode character
In [117]:han = '漢' print type(han) print len(han) str(han) Out[117]:<type 'str'> 3 <- '漢'的長度是3,明明是一個字,為什么長度是3? '\xe6\xb1\x89' <- 答案在這里
當'漢'這個字被存儲在內存中時,它會被轉為三個字符'\xe6\xb1\x89'。所以len()給出的長度是3,而不是1. 那么為了讓'漢'變成一個真正的字,我們就需要對它進行Decoding。(參看2. 把 0 和 1 轉換為Unicode的過程叫Decoding)
In [125]:str.decode(han)
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-125-3ff96a3a19da> in <module>()
----> 1 str.encode(han)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)
這里Python拋出了異常。因為默認的ASCII編碼無法Decode這個文字。因為這個文字的數值已經超過了0 - 127這個范圍。所以我們需要使用UTF-8編碼來Decode:
In [127]:str.decode(han, 'utf8') Out[127]:u'\u6c49'
這里han這個變量被成功Decode為 u'\u6c49
In [142]:uni_han = u'\u6c49' len(uni_han) Out[142]:1 <- 長度變為了正確的1
再來個示例作為結尾
猜猜這段代碼的輸出是什么:
uni_han = u'\u6c49' print '\'{0}\'的長度是{1}'.format(uni_han, len(uni_han))
結果是:
---------------------------------------------------------------------------
UnicodeEncodeError Traceback (most recent call last)
<ipython-input-143-9bec6fa25583> in <module>()
1 uni_han = u'\u6c49'
----> 2 print '\'{0}\'的長度是{1}'.format(uni_han, len(uni_han))
UnicodeEncodeError: 'ascii' codec can't encode character u'\u6c49' in position 0: ordinal not in range(128)
好傷心啊,本以為再也不會碰到這個問題了。那么問題出在哪呢?這部分代碼'\'{0}\'的長度是{1}'是str,也就是原始的字符數據。我們想把一個Unicode (uni_han)混到它們里一起打印。這時,Python信心滿滿的用了默認的ASCII編碼來Encode uni_han。結果可想而知,又是再次超出0 - 127的范圍,無法Encode。這時,我們就需要告訴Python放棄ASCII吧,請使用UTF-8:
In [145]:uni_han = u'\u6c49' print '\'{0}\'的長度是{1}'.format(unicode.encode(uni_han,'utf8'), len(uni_han)) '漢'的長度是1
另一種方法是讓前半部分的str變為Unicode:
In [150]:uni_han = u'\u6c49' print u'\'{0}\'的長度是{1}'.format(uni_han, len(uni_han)) '漢'的長度是1
總結
在Python 2.x里str就是原始的010101, Unicode是Unicode,這兩個東西不能混着用。當一個文字被寫到硬盤上時,或者打印到屏幕上時,需要使用正確的Encoding編碼。Python默認使用ASCII,但其實應該用UTF-8。這個問題以后還會經常碰到。關鍵是要理解ASCII,UTF8,Unicode, Encoding和Decoding的定義和關系。