Python中文本和字節序列的處理

字節問題

“字符串”是個相當簡單的概念:一個字符串是一個字符序列。問題出 在“字符”的定義上。

1.Unicode 標準把字符的標識和具體的字節表述進行了如下的明確區分 字符的標識,即碼位,是 0~1 114 111 的數字(十進制),在Unicode 標準中以 4~6 個十六進制數字表示,而且加前綴“U+”。例 如,字母 A 的碼位是 U+0041

**2.字符的具體表述取決於所用的編碼。編碼是在碼位和字節序列之間轉換時使用的算法。**在 UTF-8 編碼中,A(U+0041)的碼位編碼成 單個字節 \x41,而在 UTF-16LE 編碼中編碼成兩個字節\x41\x00。再舉個例子,歐元符號(U+20AC)在 UTF-8 編碼中是三個字節——\xe2\x82\xac, 而在 UTF-16LE 中編碼成兩個字 節:\xac\x20。

把碼位轉換成字節序列的過程是編碼;把字節序列轉換成碼位的過程是解碼。

#編碼和解碼
>>> 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 字符串想 成“人類可讀”的文本。那麼,把字節序列變成人類可讀的文本字符串就是解碼,而把字符串變成用於存儲或傳輸的字節序列就是編碼

字節概要

#包含5個字節的bytes和bytearray對象
>>> 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對象。

基本的編解碼器

#使用3個編解碼器編碼字符串“El Niño”,得到的字節序列差異很大
>>> for codec in ['latin_1', 'utf_8', 'utf_16']: 
     print(codec, 'El Niño'.encode(codec), sep='\t') 
 
latin_1 b'El Ni\xf1o' 
utf_8   b'El Ni\xc3\xb1o' 
utf_16  b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00'

展示不同編解碼器對“A”和高音譜號等字符編碼後得到的字節序列
在這裏插入圖片描述

latin1(即iso8859_1)
一種重要的編碼,是其他編碼的基礎,例如cp1252和Unicode(注意,latin1與cp1252的字節值是一樣的,甚至連碼位也相同)。

cp1252
Microsoft制定的latin1超集,添加了有用的符號,例如彎引號和€(歐元);有些Windows應用把它稱爲“ANSI”,但它並不是ANSI標準。

cp437
IBM PC最初的字符集,包含框圖符號。與後來出現的latin1不兼容。

gb2312
用於編碼簡體中文的陳舊標準;這是亞洲語言中使用較廣泛的多字節編碼之一。

utf-8
目前Web中最常見的8位編碼;與ASCII兼容(純ASCII文本是有效的UTF-8文本)。

utf-16le
UTF-16的16位編碼方案的一種形式;所有UTF-16支持通過轉義序列(稱爲“代理對”,surrogate pair)表示超過U+FFFF的碼位。

編碼問題

雖然有個一般性的UnicodeError異常,但是報告錯誤時幾乎都會指明具體的異常:
UnicodeEncodeError(把字符串轉換成二進制序列時)或UnicodeDecodeError(把二進制序列轉換成字符串時)

處理UnicodeEncodeError

把文本轉換成字節序列時,如果目標編碼中沒有定義某個字符,那就會拋出UnicodeEncodeError異常。

#編碼成字節序列:成功和錯誤處理
>>> city = 'São Paulo' 
>>> city.encode('utf_8') #➊ 
b'S\xc3\xa3o Paulo' 
>>> city.encode('utf_16') 
b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00' 
>>> city.encode('iso8859_1') #➋ 
b'S\xe3o Paulo' 
>>> city.encode('cp437') #➌ 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
  File "/.../lib/python3.4/encodings/cp437.py", line 12, in encode 
    return codecs.charmap_encode(input,errors,encoding_map) 
UnicodeEncodeError: 'charmap' codec can't encode character '\xe3' in 
position 1: character maps to <undefined> 
>>> city.encode('cp437', errors='ignore') #➍ 
b'So Paulo' 
>>> city.encode('cp437', errors='replace') #➎ 
b'S?o Paulo' 
>>> city.encode('cp437', errors='xmlcharrefreplace') #➏ 
b'S&#227;o Paulo'

➊ 'utf_?‘編碼能處理任何字符串。
➋ ‘iso8859_1’編碼也能處理字符串’São Paulo’。
➌ ‘cp437’無法編碼’ã’(帶波形符的“a”)。默認的錯誤處理方式’strict’拋出Unicode-
EncodeError。
➍ error=‘ignore’處理方式悄無聲息地跳過無法編碼的字符;這樣做通常很是不妥。
➎ 編碼時指定error=‘replace’,把無法編碼的字符替換成’?’;數據損壞了,但是用戶知
道出了問題。
➏ 'xmlcharrefreplace’把無法編碼的字符替換成XML實體。

處理UnicodeDecodeError

把二進制序列轉換成文本時,如果假設是這兩個編碼中的一個,遇到無法轉換的字節序列時會拋出UnicodeDecodeError。

#把字節序列解碼成字符串:成功和錯誤處理
>>> octets = b'Montr\xe9al'  #➊ 
>>> octets.decode('cp1252')  #➋ 
'Montréal' 
>>> octets.decode('iso8859_7')  #➌ 
'Montrιal' 
>>> octets.decode('koi8_r')  #➍ 
'MontrИal' 
>>> octets.decode('utf_8')  #➎ 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: 
invalid continuation byte 
>>> octets.decode('utf_8', errors='replace')  #➏ 
'Montr�al'

➊ 這些字節序列是使用latin1編碼的“Montréal”;’\xe9’字節對應“é”。
➋ 可以使用’cp1252’(Windows 1252)解碼,因爲它是latin1的有效超集。
➌ ISO-8859-7用於編碼希臘文,因此無法正確解釋’\xe9’字節,而且沒有拋出錯誤。
➍ KOI8-R用於編碼俄文;這裏,’\xe9’表示西裏爾字母“И”。
➎ 'utf_8’編解碼器檢測到octets不是有效的UTF-8字符串,拋出UnicodeDecodeError。
➏ 使用’replace’錯誤處理方式,\xe9替換成了“�”(碼位是U+FFFD),這是官方指定
的REPLACEMENT CHARACTER(替換字符),表示未知字符。

處理文本文件

處理文本的最佳實踐是“Unicode三明治”
在這裏插入圖片描述

>>> fp = open('cafe.txt', 'w', encoding='utf_8') 
>>> fp  #➊ 
<_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'> 
>>> fp.write('café') 
4  #➋ 
>>> fp.close() 
>>> import os 
>>> os.stat('cafe.txt').st_size 
5  #➌ 
>>> fp2 = open('cafe.txt') 
>>> fp2  #➍ 
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp1252'> 
>>> fp2.encoding  #➎ 
'cp1252' 
>>> fp2.read() 
>'café'  #➏ 
>>> fp3 = open('cafe.txt', encoding='utf_8')  #➐ 
>>> fp3 
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'> 
>>> fp3.read() 
'café'  #➑ 
>>> fp4 = open('cafe.txt', 'rb')  #➒ 
>>> fp4 
<_io.BufferedReader name='cafe.txt'>  #➓ 
>>> fp4.read()  #⑪
 
b'caf\xc3\xa9'

➊ 默認情況下,open函數採用文本模式,返回一個TextIOWrapper對象。
➋ 在TextIOWrapper對象上調用write方法返回寫入的Unicode字符數。
➌ os.stat報告文件中有5個字節;UTF-8編碼的’é’佔兩個字節,0xc3和0xa9。
➍ 打開文本文件時沒有顯式指定編碼,返回一個TextIOWrapper對象,編碼是區域設置中
的默認值。
➎ TextIOWrapper對象有個encoding屬性;查看它,發現這裏的編碼是cp1252。
➏ 在Windows cp1252編碼中,0xc3字節是“Ô(帶波形符的A),0xa9字節是版權符號。
➐ 使用正確的編碼打開那個文件。
➑ 結果符合預期:得到的是四個Unicode字符’café’。
➒ 'rb’標誌指明在二進制模式中讀取文件。
➓ 返回的是BufferedReader對象,而不是TextIOWrapper對象。
⑪讀取返回的字節序列,結果與預期相符。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章