1 字符問題
“字符串”是個相當簡單的概念:一個字符串是一個字符序列。問題出在“字符”的定義上。
在 2015 年,“字符”的最佳定義是 Unicode 字符。因此,從 Python 3 的str 對象中獲取的元素是 Unicode 字符,這相當於從 Python 2 的unicode 對象中獲取的元素,而不是從 Python 2 的 str 對象中獲取的原始字節序列。
Unicode 標准把字符的標識和具體的字節表述進行了如下的明確區分。
字符的標識,即碼位,是 0~1 114 111 的數字(十進制),在Unicode 標准中以 4~6 個十六進制數字表示,而且加前綴“U+”。例如,字母 A 的碼位是 U+0041,歐元符號的碼位是 U+20AC,高音譜號的碼位是 U+1D11E。在 Unicode 6.3 中(這是 Python 3.4 使用的標准),約 10% 的有效碼位有對應的字符。
字符的具體表述取決於所用的編碼。編碼是在碼位和字節序列之間轉換時使用的算法。在 UTF-8 編碼中,A(U+0041)的碼位編碼成單個字節 \x41,而在 UTF-16LE 編碼中編碼成兩個字節\x41\x00。再舉個例子,歐元符號(U+20AC)在 UTF-8 編碼中是三個字節——\xe2\x82\xac,而在 UTF-16LE 中編碼成兩個字節:\xac\x20。
把碼位轉換成字節序列的過程是編碼;把字節序列轉換成碼位的過程是解碼。示例 4-1 闡釋了這一區分。
>>> s = 'café' >>> len(s) # ➊ 4 >>> b = s.encode('utf8') # ➋ >>> b b'caf\xc3\xa9' # ➌ >>> len(b) # ➍ 5 >>> b.decode('utf8') # ➎ 'café
❶ 'café' 字符串有 4 個 Unicode 字符。
❷ 使用 UTF-8 把 str 對象編碼成 bytes 對象。
❸ bytes 字面量以 b 開頭。
❹ 字節序列 b 有 5 個字節(在 UTF-8 中,“é”的碼位編碼成兩個字節)。
❺ 使用 UTF-8 把 bytes 對象解碼成 str 對象。
如果想幫助自己記住 .decode() 和 .encode() 的區別,可以把字節序列想成晦澀難懂的機器磁芯轉儲,把 Unicode 字符串想成“人類可讀”的文本。那么,把字節序列變成人類可讀的文本字符串就是解碼,而把字符串變成用於存儲或傳輸的字節序列就是編碼。
雖然 Python 3 的 str 類型基本相當於 Python 2 的 unicode 類型,只不過是換了個新名稱,但是 Python 3 的 bytes 類型卻不是把 str 類型換個名稱那么簡單,而且還有關系緊密的 bytearray 類型。因此,在討論編碼和解碼的問題之前,有必要先來介紹一下二進制序列類型。
2 字節概要
新的二進制序列類型在很多方面與 Python 2 的 str 類型不同。首先要知道,Python 內置了兩種基本的二進制序列類型:Python 3 引入的不可變bytes 類型和 Python 2.6 添加的可變 bytearray 類型。(Python 2.6 也引入了 bytes 類型,但那只不過是 str 類型的別名,與 Python 3 的bytes 類型不同。)
>>> cafe = bytes('café', encoding='utf_8') ➊ >>> cafe b'caf\xc3\xa9' >>> cafe[0] ➋ 99 >>> cafe[:1] ➌ b'c' >>> cafe_arr = bytearray(cafe) >>> cafe_arr ➍ bytearray(b'caf\xc3\xa9') >>> cafe_arr[-1:] ➎ bytearray(b'\xa9')
❶ bytes 對象可以從 str 對象使用給定的編碼構建。
❷ 各個元素是 range(256) 內的整數。
❸ bytes 對象的切片還是 bytes 對象,即使是只有一個字節的切片。
❹ bytearray 對象沒有字面量句法,而是以 bytearray() 和字節序列字面量參數的形式顯示。
❺ bytearray 對象的切片還是 bytearray 對象。
my_bytes[0] 獲取的是一個整數,而 my_bytes[:1] 返回的是一個長度為 1 的 bytes 對象——這一點應該不會讓人意外。s[0] == s[:1] 只對 str 這個序列類型成立。不過,str 類型的這個行為十分罕見。對其他各個序列類型來說,s[i] 返回一個元素,而 s[i:i+1] 返回一個相同類型的序列,里面是 s[i] 元素。
二進制序列有個類方法是 str 沒有的,名為 fromhex,它的作用是解析十六進制數字對(數字對之間的空格是可選的),構建二進制序列:
>>> bytes.fromhex('31 4B CE A9') b'1K\xce\xa9'
構建 bytes 或 bytearray 實例還可以調用各自的構造方法,傳入下述參數。
一個 str 對象和一個 encoding 關鍵字參數。
一個可迭代對象,提供 0~255 之間的數值。
一個整數,使用空字節創建對應長度的二進制序列。[Python 3.5 會把這個構造方法標記為“過時的”,Python 3.6 會將其刪除。參見“PEP 467—Minor API improvements for binarysequences”(https://www.python.org/dev/peps/pep-0467/)。]
一個實現了緩沖協議的對象(如bytes、bytearray、memoryview、array.array);此時,把源對象中的字節序列復制到新建的二進制序列中。
結構體和內存視圖
struct 模塊提供了一些函數,把打包的字節序列轉換成不同類型字段組成的元組,還有一些函數用於執行反向轉換,把元組轉換成打包的字節序列。struct 模塊能處理 bytes、bytearray 和 memoryview 對象。
memoryview 類不是用於創建或存儲字節序列的,而是共享內存,讓你訪問其他二進制序列、打包的數組和緩沖中的數據切片,而無需復制字節序列,例如 Python Imaging Library(PIL) 就是這樣處理圖像的。
示例 4-4 展示了如何使用 memoryview 和 struct 提取一個 GIF 圖像的寬度和高度。
>>> import struct >>> fmt = '<3s3sHH' # ➊ >>> with open('filter.gif', 'rb') as fp: ... img = memoryview(fp.read()) # ➋ ... >>> header = img[:10] # ➌ >>> bytes(header) # ➍ b'GIF89a+\x02\xe6\x00' >>> struct.unpack(fmt, header) # ➎ (b'GIF', b'89a', 555, 230) >>> del header # ➏ >>> del img
❶ 結構體的格式:< 是小字節序,3s3s 是兩個 3 字節序列,HH 是兩個16 位二進制整數。
❷ 使用內存中的文件內容創建一個 memoryview 對象……
❸ ……然后使用它的切片再創建一個 memoryview 對象;這里不會復制字節序列。
❹ 轉換成字節序列,這只是為了顯示;這里復制了 10 字節。
❺ 拆包 memoryview 對象,得到一個元組,包含類型、版本、寬度和高度。
❻ 刪除引用,釋放 memoryview 實例所占的內存。
