iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 8
0
Data Technology

30天python雜談系列 第 8

UnicodeError雜談之二———初探字符串與位元組的交互作用

python UnicodeError雜談之二

囉唆一下:因為python對於是否為unicode編碼的字串有不一樣的物件類型名稱,但在往後若一直'unicode編碼'、'非unicode編碼'的感覺好繞口阿,所以在這裡正名一下,凡unicode編碼的字串因為比較符合字元的特性,所以統一叫他們為'字符串',而非unicode編碼的字串,因為適合作為儲存以及數據傳輸,因此統稱為'位元組字串'。

昨天說到unicode的起源以及python如何看待不同編碼的字串物件,今天就來實際講一下UnicodeError或是其他編碼問題會遇到的情境,並提出解決方法,另外,因為python2和python3在這一方面會有些微不同,因此會稍微分開還解釋。

情境一:程式碼中含有中文字元

說明:
假設現在有一個test.py檔,裏面只有一行簡單的指令:print('我是一個範例'),如果試著用python2執行他,會出現:SyntaxError: Non-ASCII character '\xe6' in file test.py on line 1, but no encoding declared

解決方法:
會出現這個錯誤,是因為python2預設以ascii編碼(個人認為功能最狹隘最會出error的原始編碼QQ)來解讀要執行的.py檔,因為ascii編碼在8bit的空間中只用到0000 0000~0111 1110的部份,所以只要在檔案裡出現中文字元,通常就會因為出現ascii編碼以外的字元(比如說範例的'\xe6' -> 1110 0110)而出現錯誤,解決方法就是添加編碼宣告# -- coding: utf-8 --,讓python直譯器了解要用utf-8編碼來解讀,或是你自己設定你想要指明的編碼。

但在python3情境一的問題並不會出現,因為在python3已經改為預設以utf-8編碼來解讀檔案:

In python2 shell:
>>> import sys
>>> sys.getdefaultencoding()
'ascii'

In python3 shell:
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'

詳細說明可以參閱PEP文件:https://www.python.org/dev/peps/pep-3120/。

情境二:轉碼函數使用

說明:
這是比較基礎的函數使用議題,不管是python2還是python3都是一樣的,從位元組字串轉到字符串叫作解碼(decode),所以是用decode函數,反之則是編碼(encode),用encode函數:

In python2 shell:
>>> a = '範例'
>>> a # 這是utf-8字串(位元組字串)
'\xe7\xaf\x84\xe4\xbe\x8b'
>>> a.decode('utf-8') # 解碼成字符串,可以看到輸出字串前面會多加u(unicode)前綴
u'\u7bc4\u4f8b'
>>> a.decode('utf-8').encode('utf-8') # 再編碼成utf-8字串(位元組字串)
'\xe7\xaf\x84\xe4\xbe\x8b'

In python3 shell:
>>> a = '範例'
>>> a # 注意 這和python2不同,當一個變數被賦予一個字串常量時,python3會將其解碼為字符串,所以這是一個字符串
'範例'
>>> a.encode('utf-8') # 編碼成utf-8字串(位元組字串),可以看到輸出字串前面會多加b(bytes)前綴
b'\xe7\xaf\x84\xe4\xbe\x8b' 
>>> a.encode('utf-8').decode('utf-8') # 再解碼成字符串
'範例'

仔細看一下python2和python3對於輸出的行為也不太一樣,但這留待以後講解python2和python3不同之處時再提及,目前只要留意encode和decode的使用就好。

情境三:讓unicode字串物件和其他編碼字串物件相加

說明:
在python3中這是嚴格禁止的,因為這兩個是不一樣類型的物件,若這樣操作會出現TypeError,而在python2會自動幫你作decode的動作,把所有字串物件轉為字符串再做相加,但因為python2預設以'ascii'來解碼你的字串物件,所以如果你的原編碼並不是'ascii',會出現UnicodeDecodeError:

In python2 shell:
>>> a = u'範例' # 這是字符串
>>> b = '範例' # 這是utf-8字串
>>> a+b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 0: ordinal not in range(128)

In python3 shell:
>>> a = '範例' # 這是字符串
>>> b = '範例'.encode('utf-8') # 這是utf-8字串  
>>> a+b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly

這個行為在python3和python2中出錯的原因並不一樣,希望大家藉由這例子更為了解這件事,在處理字串時,還是要好好規範字串的編碼,像昨天所說的,進入python程式的文字儘量都解碼成unicode字串來做處理,不要讓兩種類型的字串混合處理(雖然在python2中這不一定會出錯),當程式越長越大的時候,才不會像踩地雷一樣,不知道那邊又會爆出UnicodeError。

今天的雜談就先到這裡,明天再來詳細解說關於讀檔的編碼問題。


上一篇
UnicodeError雜談之一———沒踩過這坑別說你用過python
下一篇
UnicodeError雜談之三———快點投靠python3啦啦啦
系列文
30天python雜談30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言