Python 編碼為什麼那麼蛋疼?
據說,每個做 Python 開發的都被字元編碼的問題搞暈過,最常見的錯誤就是 UnicodeEncodeError、UnicodeDecodeError,你好像知道怎麼解決,遺憾的是,錯誤又出現在其它地方,問題總是重蹈覆轍,str 到 unicode 之間的轉換用 decode 還是 encode 方法還特不好記,老是混淆,問題究竟出在哪裡?
為了弄清楚這個問題,我決定從 python 字串的構成以及字元編碼的細節上進行深入淺出的分析
位元組與字元
計算機儲存的一切資料,文字字元、圖片、視訊、音訊、軟體都是由一串01的位元組序列構成的,一個位元組等於8個位元位。
而字元就是一個符號,比如一個漢字、一個英文字母、一個數字、一個標點都可以稱為一個字元。
位元組方便儲存和網路傳輸,而字元用於顯示,方便閱讀。例如字元 “p” 儲存到硬碟是一串二進位制資料 01110000
,佔用一個位元組的長度
編碼與解碼
我們用編輯器開啟的文字,看到的一個個字元,最終儲存在磁碟的時候都是以二進位制位元組序列形式存起來的。那麼從字元到位元組的轉換過程就叫做編碼(encode),反過來叫做解碼(decode),兩者是一個可逆的過程。編碼是為了儲存傳輸,解碼是為了方便顯示閱讀。
例如字元 “p” 經過編碼處理儲存到硬碟是一串二進位制位元組序列 01110000 ,佔用一個位元組的長度。字元 “禪” 有可能是以 “11100111 10100110 10000101″ 佔用3個位元組的長度儲存,為什麼說是有可能呢?這個放到後面再說。
Python 的編碼為什麼那麼蛋疼?當然,這不能怪開發者。
這是因為 Python2 使用 ASCII 字元編碼作為預設編碼方式,而 ASCII 不能處理中文,那麼為什麼不用 UTf-8 呢?因為 Guido 老爹為 Python 編寫第一行程式碼是在1989年的冬天,1991年2月正式開源釋出了第一個版本,而 Unicode 是1991年10月釋出的,也就是說 Python 這門語言創立的時候 UTF-8 還沒誕生,這是其一。
Python 把字串的型別還搞成兩種,unicode 和 str ,以至於把開發者都弄糊塗了,這是其二。python3 徹底把 字串重新改造了,只保留一種型別,這是後話,以後再說。
str與unicode
Python2 把字串分為 unicode 和 str 兩種型別。本質上 str 是一串二進位制位元組序列,下面的示例程式碼可以看出 str 型別的 “禪” 列印出來是十六進位制的 \xec\xf8 ,對應的二進位制位元組序列就是 ’11101100 11111000′。
>>> s = '禪' >>> s '\xec\xf8' >>> type(s) <type 'str'>
而 unicode 型別的 u”禪” 對應的 unicode 符號是 u’\u7985′
>>> u = u"禪" >>> u u'\u7985' >>> type(u) <type 'unicode'>
我們要把 unicode 符號儲存到檔案或者傳輸到網路就需要經過編碼處理轉換成 str 型別,於是 python 提供了 encode 方法,從 unicode 轉換到 str,反之亦然。
encode
>>> u = u"禪" >>> u u'\u7985' >>> u.encode("utf-8") '\xe7\xa6\x85'
decode
>>> s = "禪" >>> s.decode("utf-8") u'\u7985' >>>
不少初學者怎麼也記不住 str 與 unicode 之間的轉換用 encode 還是 decode,如果你記住了 str 本質上其實是一串二進位制資料,而 unicode 是字元(符號),編碼(encode)就是把字元(符號)轉換為 二進位制資料的過程,因此 unicode 到 str 的轉換要用 encode 方法,反過來就是用 decode 方法。
encoding always takes a Unicode string and returns a bytes sequence, and decoding always takes a bytes sequence and returns a Unicode string”.
清楚了 str 與 unicode 之間的轉換關係之後,我們來看看什麼時候會出現 UnicodeEncodeError、UnicodeDecodeError 錯誤。
UnicodeEncodeError
UnicodeEncodeError 發生在 unicode 字串轉換成 str 位元組序列的時候,來看一個例子,把一串 unicode 字串儲存到檔案
# -*- coding:utf-8 -*- def main(): name = u'Python之禪' f = open("output.txt", "w") f.write(name)
錯誤日誌
UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 6-7: ordinal not in range(128)
為什麼會出現 UnicodeEncodeError?
因為呼叫 write 方法時,Python 會先判斷字串是什麼型別,如果是 str,就直接寫入檔案,不需要編碼,因為 str 型別的字串本身就是一串二進位制的位元組序列了。
如果字串是 unicode 型別,那麼它會先呼叫 encode 方法把 unicode 字串轉換成二進位制形式的 str 型別,才儲存到檔案,而 encode 方法會使用 python 預設的 ascii 碼來編碼
相當於:
>>> u"Python之禪".encode("ascii")
但是,我們知道 ASCII 字符集中只包含了128個拉丁字母,不包括中文字元,因此 出現了 ‘ascii’ codec can’t encode characters 的錯誤。要正確地使用 encode ,就必須指定一個包含了中文字元的字符集,比如:UTF-8、GBK。
>>> u"Python之禪".encode("utf-8") 'Python\xe4\xb9\x8b\xe7\xa6\x85' >>> u"Python之禪".encode("gbk") 'Python\xd6\xae\xec\xf8'
所以要把 unicode 字串正確地寫入檔案,就應該預先把字串進行 UTF-8 或 GBK 編碼轉換。
def main(): name = u'Python之禪' name = name.encode('utf-8') with open("output.txt", "w") as f: f.write(name)
當然,把 unicode 字串正確地寫入檔案不止一種方式,但原理是一樣的,這裡不再介紹,把字串寫入資料庫,傳輸到網路都是同樣的原理
UnicodeDecodeError
UnicodeDecodeError 發生在 str 型別的位元組序列解碼成 unicode 型別的字串時
>>> a = u"禪" >>> a u'\u7985' >>> b = a.encode("utf-8") >>> b '\xe7\xa6\x85' >>> b.decode("gbk") Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'gbk' codec can't decode byte 0x85 in position 2: incomplete multibyte sequence
把一個經過 UTF-8 編碼後生成的位元組序列 ‘\xe7\xa6\x85′ 再用 GBK 解碼轉換成 unicode 字串時,出現 UnicodeDecodeError,因為 (對於中文字元)GBK 編碼只佔用兩個位元組,而 UTF-8 佔用3個位元組,用 GBK 轉換時,還多出一個位元組,因此它沒法解析。避免 UnicodeDecodeError 的關鍵是保持 編碼和解碼時用的編碼型別一致。
這也回答了文章開頭說的字元 “禪”,儲存到檔案中有可能佔3個位元組,有可能佔2個位元組,具體處決於 encode 的時候指定的編碼格式是什麼。
再舉一個 UnicodeDecodeError 的例子
>>> x = u"Python" >>> y = "之禪" >>> x + y Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) >>>
str 與 unicode 字串 執行 + 操作是,Python 會把 str 型別的位元組序列隱式地轉換成(解碼)成 和 x 一樣的 unicode 型別,但Python是使用預設的 ascii 編碼來轉換的,而 ASCII 中不包含中文,所以報錯了。
>>> y.decode('ascii') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
正確地方式應該是顯示地把 y 用 UTF-8 或者 GBK 進行解碼。
>>> x = u"Python" >>> y = "之禪" >>> y = y.decode("utf-8") >>> x + y u'Python\u4e4b\u7985'
以上內容都是基於 Python2 來講的,關於 Python3 的字元和編碼將會另開一篇文章來寫,保持關注。
相關文章
- 為什麼 python 那麼熱門Python
- 為什麼Python語言那麼受歡迎呢?Python
- Redis為什麼那麼快?Redis
- NER為什麼那麼難
- 哪有那麼多為什麼?
- 為什麼現在Python那麼火?分享38集Python課程Python
- Kafka為什麼速度那麼快?Kafka
- ot 這個蛋疼的快取是怎麼回事快取
- 人人學Python,為什麼就業拿高薪的那麼少?Python就業高薪
- 破玩意 | Redis 為什麼那麼快Redis
- 為什麼SSL證書那麼貴?
- 文字編輯工具那麼多,運維為什麼要學vi/vim?運維
- 為什麼那麼多零基礎人員學習Python?Python
- 人工智慧為什麼那麼火 Python就業薪資怎麼樣人工智慧Python就業
- 物聯網路卡為什麼那麼火
- 為什麼資料備份那麼重要?
- 為什麼Tesla顯示卡那麼貴
- 低程式碼開發平臺為什麼那麼受歡迎
- 為什麼python會火?吸引那麼多人零基礎學習Python
- discuQ 這個蛋為什麼沒有人喜歡?
- 什麼是Python?Python為什麼這麼搶手?Python
- Python是什麼?為什麼要掌握python?Python
- 可別在程式碼中寫那麼多魔法值了,腦殼疼!
- 為什麼遊戲DLC的精品那麼少?遊戲
- 為什麼bootstrap不再那麼受歡迎了?boot
- iOS應用加固為什麼也那麼重要?iOS
- 遊戲的留存為什麼那麼難調?遊戲
- 京東二面,Redis為什麼那麼快?Redis
- 為什麼Scrum變得不那麼重要了? - LogRocketScrum
- 為什麼有那麼多人選擇“人工智慧”,真的有那麼好嗎?人工智慧
- Python是什麼?為什麼Python受歡迎?Python
- Python3中預設編碼是什麼?怎麼用?Python
- 為什麼 Python 這麼慢?Python
- 為什麼Python這麼慢?Python
- 大齡碼農經驗那麼豐富,為什麼很多公司都不招?
- 為什麼 Django 後臺管理系統那麼“醜”?Django
- 自動化測試落地為什麼那麼難
- 在遊戲裡“翻盤”為什麼那麼難?遊戲
- 2020年了,為什麼IT行業還那麼“吃香”?行業