摘要:只包含了個基本拉丁字母阿拉伯?dāng)?shù)目字和英式標點符號一共個字符,因此只需要不占滿一個字節(jié)就可以存儲,而則涵蓋的數(shù)據(jù)除了視覺上的字形編碼方法標準的字符編碼外,還包含了字符特性,如大小寫字母,共可包含個字符,而到現(xiàn)在只填充了其中的個位置。
項目地址:https://git.io/pytips
0x07 和 0x08 分別介紹了 Python 中的字符串類型(str)和字節(jié)類型(byte),以及 Python 編碼中最常見也是最頑固的兩個錯誤:
UnicodeEncodeError: "ascii" codec can"t encode characters in position 0-1: ordinal not in range(128)
UnicodeDecodeError: "utf-8" codec can"t decode bytes in position 0-1: invalid continuation byte
這一期就從這兩個錯誤入手,分析 Python 中 Unicode 的正確用法。這篇短文并不能保證你可以永遠杜絕上面兩個錯誤,但是希望在下次遇到這個錯誤的時候知道錯在哪里、應(yīng)該從哪里入手。
編碼與解碼上面的兩個錯誤分別是 UnicodeEncodeError 和 UnicodeDecodeError,也就是說分別在 Unicode 編碼(Encode)和解碼(Decode)過程中出現(xiàn)了錯誤,那么編碼和解碼究竟分別意味著什么?根據(jù)維基百科字符編碼的定義:
字符編碼(英語:Character encoding)、字集碼是把字符集中的字符編碼為指定集合中某一對象(例如:比特模式、自然數(shù)序列、8位組或者電脈沖),以便文本在計算機中存儲和通過通信網(wǎng)絡(luò)的傳遞。
簡單來說就是把人類通用的語言符號翻譯成計算機通用的對象,而反向的翻譯過程自然就是解碼了。Python 中的字符串類型代表人類通用的語言符號,因此字符串類型有encode()方法;而字節(jié)類型代表計算機通用的對象(二進制數(shù)據(jù)),因此字節(jié)類型有decode()方法。
print("??".encode())
b"xf0x9fx8cx8exf0x9fx8cx8f"
print(b"xf0x9fx8cx8exf0x9fx8cx8f".decode())
??
既然說編碼和解碼都是翻譯的過程,那么就需要一本字典將人類和計算機的語言一一對應(yīng)起來,這本字典的名字叫做字符集,從最早的 ASCII 到現(xiàn)在最通用的 Unicode,它們的本質(zhì)是一樣的,只是兩本字典的厚度不同而已。ASCII 只包含了26個基本拉丁字母、阿拉伯?dāng)?shù)目字和英式標點符號一共128個字符,因此只需要(不占滿)一個字節(jié)就可以存儲,而 Unicode 則涵蓋的數(shù)據(jù)除了視覺上的字形、編碼方法、標準的字符編碼外,還包含了字符特性,如大小寫字母,共可包含 1.1M 個字符,而到現(xiàn)在只填充了其中的 110K 個位置。
字符集中字符所存儲的位置(或者說對應(yīng)的計算機通用的數(shù)字)稱之為碼位(code point),例如在 ASCII 中字符 "$" 的碼位就是:
print(ord("$"))
36
ASCII 只需要一個字節(jié)就能存下所有碼位,而 Unicode 則需要幾個字節(jié)才能容納,但是對于具體采用什么樣的方案來實現(xiàn) Unicode 的這種映射關(guān)系,也有很多不同的方案(或規(guī)則),例如最常見(也是 Python 中默認的)UTF-8,還有 UTF-16、UTF-32 等,對于它們規(guī)則上的不同這里就不深入展開了。當(dāng)然,在 ASCII 與 Unicode 之間還有很多其他的字符集與編碼方案,例如中文編碼的 GB2312、繁體字的 Big5 等等,這并不影響我們對編碼與解碼過程的理解。
Unicode*Error明白了字符串與字節(jié),編碼與解碼之后,讓我們手動制造上面兩個 Unicode*Error 試試,首先是編碼錯誤:
def tryEncode(s, encoding="utf-8"): try: print(s.encode(encoding)) except UnicodeEncodeError as err: print(err) s = "$" # UTF-8 String tryEncode(s) # 默認用 UTF-8 進行編碼 tryEncode(s, "ascii") # 嘗試用 ASCII 進行編碼 s = "雨" # UTF-8 String tryEncode(s) # 默認用 UTF-8 進行編碼 tryEncode(s, "ascii") # 嘗試用 ASCII 進行編碼 tryEncode(s, "GB2312") # 嘗試用 GB2312 進行編碼
b"$" b"$" b"xe9x9bxa8" "ascii" codec can"t encode character "u96e8" in position 0: ordinal not in range(128) b"xd3xea"
由于 UTF-8 對 ASCII 的兼容性,"$" 可以用 ASCII 進行編碼;而 "雨" 則無法用 ASCII 進行編碼,因為它已經(jīng)超出了 ASCII 字符集的 128 個字符,所以引發(fā)了 UnicodeEncodeError;而 "雨" 在 GB2312 中的碼位是 b"xd3xea",與 UTF-8 不同,但是仍然可以正確編碼。因此如果出現(xiàn)了 UnicodeEncodeError 說明你用錯了字典,要翻譯的字符沒辦法正確翻譯成碼位!
再來看解碼錯誤:
def tryDecode(s, decoding="utf-8"): try: print(s.decode(decoding)) except UnicodeDecodeError as err: print(err) b = b"$" # Bytes tryDecode(b) # 默認用 UTF-8 進行解碼 tryDecode(b, "ascii") # 嘗試用 ASCII 進行解碼 tryDecode(b, "GB2312") # 嘗試用 GB2312 進行解碼 b = b"xd3xea" # 上面例子中通過 GB2312 編碼得到的 Bytes tryDecode(b) # 默認用 UTF-8 進行解碼 tryDecode(b, "ascii") # 嘗試用 ASCII 進行解碼 tryDecode(b, "GB2312") # 嘗試用 GB2312 進行解碼 tryDecode(b, "GBK") # 嘗試用 GBK 進行解碼 tryDecode(b, "Big5") # 嘗試用 Big5 進行解碼 tryDecode(b.decode("GB2312").encode()) # Byte-Decode-Unicode-Encode-Byte
$ $ $ "utf-8" codec can"t decode byte 0xd3 in position 0: invalid continuation byte "ascii" codec can"t decode byte 0xd3 in position 0: ordinal not in range(128) 雨 雨 迾 雨
一般后續(xù)出現(xiàn)的字符集都是對 ASCII 兼容的,可以認為 ASCII 是他們的一個子集,因此可以用 ASCII 進行解碼(編碼)的,一般也可以用其它方法;對于不是不存在子集關(guān)系的編碼,強行解碼有可能會導(dǎo)致錯誤或亂碼!
實踐中的策略清楚了上面介紹的所有原理之后,在時間操作中應(yīng)該怎樣規(guī)避錯誤或亂碼呢?
記清楚編碼與解碼的方向;
在 Python 中的操作盡量采用 UTF-8,輸入或輸出的時候再根據(jù)需求確定是否需要編碼成二進制:
# cat utf8.txt # 你好,世界! # file utf8.txt # utf8.txt: UTF-8 Unicode text with open("utf8.txt", "rb") as f: content = f.read() print(content) print(content.decode()) with open("utf8.txt", "r") as f: print(f.read()) # cat gb2312.txt # 你好,Unicode! # file gb2312.txt # gb2312.txt: ISO-8859 text with open("gb2312.txt", "r") as f: try: print(f.read()) except: print("Failed to decode file!") with open("gb2312.txt", "rb") as f: print(f.read().decode("gb2312"))
b"xe4xbdxa0xe5xa5xbdxefxbcx8cxe4xb8x96xe7x95x8cxefxbcx81 " 你好,世界! 你好,世界! Failed to decode file! 你好,Unicode!
歡迎關(guān)注 PyHub!
Pragmatic Unicode
字符編碼筆記:ASCII,Unicode和UTF-8
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/37832.html
摘要:回到對字節(jié)和字節(jié)數(shù)組的定義為了用計算機可以理解的數(shù)字描述人類使用的字符,我們需要一張數(shù)字與字符對應(yīng)的表。由于和字符串一樣是序列類型,字節(jié)和字節(jié)數(shù)組可用的方法也類似,這里就不一一列舉了。 項目地址:https://git.io/pytips 0x07 中介紹了 Python 中的字符串類型,字符串類型是對人類友好的符號,但計算機只認識一種符號,那就是二進制(binary)數(shù),或者說是數(shù)字...
摘要:項目地址所有用過的人應(yīng)該都看過下面兩行錯誤信息這就是界的錕斤拷今天和接下來幾期的內(nèi)容將主要關(guān)注中的字符串字節(jié)及兩者之間的相互轉(zhuǎn)換。 項目地址:https://git.io/pytips 所有用過 Python (2&3)的人應(yīng)該都看過下面兩行錯誤信息: UnicodeEncodeError: ascii codec cant encode characters in position...
摘要:模塊的導(dǎo)入一定要放在最上方,也就是在所有其它模塊之前導(dǎo)入。最后一列是每個新特性所對應(yīng)的及簡單描述。相對導(dǎo)入則可以使用為標記導(dǎo)入相對目錄中的模塊,具體可以參考這篇文章導(dǎo)入模塊的幾種姿勢。 項目地址:https://git.io/pytips 我們經(jīng)常從一些組織良好的 Python 項目中看到 __future__ 的身影,例如: from __future__ import absolu...
摘要:可以通過一個簡單的例子來展示當(dāng)然,也可以用狀態(tài)變量的做法來替代總結(jié)有人覺得的這些用法違反直覺或者是而非,不值得提倡。 項目地址:https://git.io/pytips 我們都知道 Python 中 else 的基本用法是在條件控制語句中的 if...elif...else...,但是 else 還有兩個其它的用途,一是用于循環(huán)的結(jié)尾,另一個是用在錯誤處理的 try 中。這原本是 P...
摘要:中的枚舉類型枚舉類型可以看作是一種標簽或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期月份狀態(tài)等。 Python 中的枚舉類型 枚舉類型可以看作是一種標簽或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期、月份、狀態(tài)等。Python 的原生類型(Built-in types)里并沒有專門的枚舉類型,但是我們可以通過很多方法來實現(xiàn)它,例如字典、類等: WEEKD...
閱讀 1379·2021-10-19 11:42
閱讀 723·2021-09-22 16:04
閱讀 1872·2021-09-10 11:23
閱讀 1848·2021-07-29 14:48
閱讀 1251·2021-07-26 23:38
閱讀 2817·2019-08-30 15:54
閱讀 1027·2019-08-30 11:25
閱讀 1700·2019-08-29 17:23