Python語言是由Guido van Rossum大牛在1989年發(fā)明,它是當今世界受歡迎的計算機編程語言之一,也是一門“學了有用、學了能用、學會能久用”的計算生態(tài)語言。
為此,CSDN作為國內大的IT中文社區(qū),特向廣大Python愛好者開設了Python學習班,幫助大家在學習的道路上少走彎路,事半功倍。3月21號晚上8點,我們特邀請知名Python技術專家劉志軍老師在班級里舉行分享活動。
劉志軍,6年開發(fā)經(jīng)驗,曾就職中興、博雅互動。擅長Web技術架構,對爬蟲、數(shù)據(jù)挖掘領域興趣濃厚。目前在一家大型醫(yī)藥集團從事數(shù)據(jù)分析工作。微信公眾號:Python之禪(vttalk)
以下為昨晚的分享內容:
為什么要聊這個話題呢?
據(jù)說,每個做 Python 開發(fā)的都被字符編碼的問題搞暈過,常見的錯誤就是 UnicodeEncodeError、UnicodeDecodeError,你好像知道怎么解決,遺憾的是,錯誤又出現(xiàn)在其它地方,問題總是重蹈覆轍,str 到 unicode 之間的轉換用 decode 還是 encode 方法還特不好記,老是混淆,問題究出在哪里?
為了弄清楚這個問題,我決定從 python 字符串的構成以及字符編碼的細節(jié)上進行深入淺出的分析。
字節(jié)與字符
計算機存儲的一切數(shù)據(jù),文本字符、圖片、視頻、音頻、軟件都是由一串01的字節(jié)序列構成的,一個字節(jié)等于8個比特位。
而字符就是一個符號,比如一個漢字、一個英文字母、一個數(shù)字、一個標點都可以稱為一個字符。
字節(jié)方便存儲和網(wǎng)絡傳輸,而字符用于顯示,方便閱讀。例如字符 “p” 存儲到硬盤是一串二進制數(shù)據(jù) 01110000,占用一個字節(jié)的長度。
編碼與解碼
我們用編輯器打開的文本,看到的一個個字符,終保存在磁盤的時候都是以二進制字節(jié)序列形式存起來的。那么從字符到字節(jié)的轉換過程就叫做編碼(encode),反過來叫做解碼(decode),兩者是一個可逆的過程。編碼是為了存儲傳輸,解碼是為了方便顯示閱讀。
例如字符 “p” 經(jīng)過編碼處理保存到硬盤是一串二進制字節(jié)序列 01110000 ,占用一個字節(jié)的長度。字符 “禪” 有可能是以 “11100111 10100110 10000101” 占用3個字節(jié)的長度存儲,為什么說是有可能呢?這個放到后面再說。
Python 的編碼為什么那么蛋疼?當然,這不能怪開發(fā)者。
這是因為 Python2 使用 ASCII 字符編碼作為默認編碼方式,而 ASCII 不能處理中文。
為什么不用 UTf-8 呢?因為 Guido 老爹為 Python 編寫行代碼是在1989年的冬天,1991年2月正式開源發(fā)布了個版本,而 Unicode 是1991年10月發(fā)布的,也就是說 Python 這門語言創(chuàng)立的時候 UTF-8 還沒誕生,這是其一。
這因為如此,Python 把字符串的類型搞成兩種,unicode 和 str ,以至于開發(fā)者都在這里載了跟頭,這是其二。
python3 徹底把字符串的實現(xiàn)重新改造了,只保留一種類型,這是后話,以后再說。
str與unicode
Python2 把字符串分為兩種類型,分別是 unicode 和 str。str 本質上是一串二進制字節(jié)序列,下面的示例代碼可以看出 str 類型的 “禪” 打印出來是十六進制的 \xec\xf8 ,對應的二進制字節(jié)序列就是 ‘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 符號保存文件或者傳輸?shù)骄W(wǎng)絡就需要經(jīng)過編碼處理轉換成 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 本質上其實是一串二進制數(shù)據(jù),而 unicode 是字符(符號),編碼(encode)就是把字符(符號)轉換為二進制數(shù)據(jù)的過程,因此 unicode 到 str 的轉換要用 encode 方法,反過來就是用 decode 方法。
清楚了str與unicode之間的轉換關系之后,我們來看看什么時候會出現(xiàn)UnicodeEncodeError、UnicodeDecodeError錯誤。
UnicodeEncodeError
UnicodeEncodeError 發(fā)生在 unicode 字符串轉換成 str 字節(jié)序列的時候,來看一個例子,把一串 unicode 字符串保存到文件。
# -*- coding:utf-8 -*- def main(): name = u'Python之禪' f = open("output.txt", "w") f.write(name)
錯誤日志
UnicodeEncodeError: ‘a(chǎn)scii’ codec can’t encode characters in position 6-7: ordinal not in range(128)
為什么會出現(xiàn) UnicodeEncodeError?
因為調用 write 方法時,Python 會先判斷字符串是什么類型,如果是 str,就直接寫入文件,不需要編碼,因為 str 類型的字符串本身就是一串二進制的字節(jié)序列了。
如果字符串是 unicode 類型,那么它會先調用 encode 方法把 unicode 字符串轉換成二進制形式的 str 類型,才保存到文件,而 encode 方法會使用 python 默認的 ascii 碼來編碼,相當于:
>>> u"Python之禪".encode("ascii")
但是,我們知道 ASCII 字符集中只包含了128個拉丁字母,不包括中文字符,因此出現(xiàn)了 ‘a(chǎn)scii’ 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 字符串正確地寫入文件不止一種方式,但原理是一樣的,這里不再介紹,把字符串寫入數(shù)據(jù)庫,傳輸?shù)骄W(wǎng)絡都是同樣的原理。
UnicodeDecodeError
UnicodeDecodeError 發(fā)生在 str 類型的字節(jié)序列解碼成 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
把一個經(jīng)過 UTF-8 編碼后生成的字節(jié)序列 ‘\xe7\xa6\x85’ 再用 GBK 解碼轉換成 unicode 字符串時,出現(xiàn)UnicodeDecodeError,因為(對于中文字符)GBK 編碼只占用兩個字節(jié),而 UTF-8 占用3個字節(jié),用 GBK 轉換時,還多出一個字節(jié),因此它沒法解析。避免 UnicodeDecodeError 的關鍵是保持編碼和解碼時用的編碼類型一致。
這也回答了文章開頭說的字符 “禪”,保存到文件中有可能占3個字節(jié),有可能占2個字節(jié),具體處決于 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 字符串執(zhí)行 + 操作是,Python 會把 str 類型的字節(jié)序列隱式地轉換成(解碼)成和 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'
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網(wǎng)站上部分文章為轉載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯(lián)系我們,我們將根據(jù)著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。