Python 編碼爲何那麼蛋疼?

聽說,每一個作 Python 開發的都被字符編碼的問題搞暈過,最多見的錯誤就是 UnicodeEncodeError、UnicodeDecodeError,你好像知道怎麼解決,遺憾的是,錯誤又出如今其它地方,問題老是重蹈覆轍,str 到 unicode 之間的轉換用 decode 仍是 encode 方法還特很差記,總是混淆,問題究竟出在哪裏?html

爲了弄清楚這個問題,我決定從 python 字符串的構成以及字符編碼的細節上進行深刻淺出的分析python

字節與字符

計算機存儲的一切數據,文本字符、圖片、視頻、音頻、軟件都是由一串01的字節序列構成的,一個字節等於8個比特位。數據庫

而字符就是一個符號,好比一個漢字、一個英文字母、一個數字、一個標點均可以稱爲一個字符。網絡

字節方便存儲和網絡傳輸,而字符用於顯示,方便閱讀。例如字符 "p" 存儲到硬盤是一串二進制數據 01110000,佔用一個字節的長度編輯器

編碼與解碼

咱們用編輯器打開的文本,看到的一個個字符,最終保存在磁盤的時候都是以二進制字節序列形式存起來的。那麼從字符到字節的轉換過程就叫作編碼(encode),反過來叫作解碼(decode),二者是一個可逆的過程。編碼是爲了存儲傳輸,解碼是爲了方便顯示閱讀。ui

例如字符 "p" 通過編碼處理保存到硬盤是一串二進制字節序列 01110000 ,佔用一個字節的長度。字符 "禪" 有多是以 "11100111 10100110 10000101" 佔用3個字節的長度存儲,爲何說是有可能呢?這個放到後面再說。編碼

Python 的編碼爲何那麼蛋疼?固然,這不能怪開發者。spa

這是由於 Python2 使用 ASCII 字符編碼做爲默認編碼方式,而 ASCII 不能處理中文,那麼爲何不用 UTf-8 呢?由於 Guido 老爹爲 Python 編寫第一行代碼是在1989年的冬天,1991年2月正式開源發佈了第一個版本,而 Unicode 是1991年10月發佈的,也就是說 Python 這門語言創立的時候 UTF-8 還沒誕生,這是其一。.net

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,反之亦然。

python-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-八、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 的字符和編碼將會另開一篇文章來寫,保持關注。

博客:foofish.net/why-python-…
公衆號:Python之禪 (id:VTtalk),分享 Python 等技術乾貨

python之禪
相關文章
相關標籤/搜索