1. 程式人生 > >Unicode、bytes和bytearray

Unicode、bytes和bytearray

Unicode、bytes和bytearray
每個Unicode字元都用一個碼點(code point)表示,而碼點是Unicode標準給每個字元指定的數字。這讓你能夠以任何現代軟體都能識別的方式表示129個文字系統中的12萬個以上的字元。當然,鑑於計算機鍵盤不可能包含幾十萬個鍵,因此有一種指定Unicode字元的通用機制:使用16或32位的十六進位制字面量(分別加上字首\u或\U)或者使用字元的Unicode名稱
(\N{name})。

“\u00C6”
‘Æ’

“\U0001F60A”
'☺ ’

“This is a cat: \N{Cat}”
'This is a cat: ’
要獲悉字元的Unicode碼點和名稱,可在網上使用有關該字元的描述進行搜尋,也可參閱特定的網站,如http://unicode-table.com。
Unicode的理念很簡單,卻帶來了一些挑戰,其中之一是編碼問題。在記憶體和磁碟中,所有物件都是以二進位制數字(0和1)表示的(這些數字每8個為一組,即1位元組),字串也不例外。在諸如C等程式語言中,這些位元組完全暴露,而字串不過是位元組序列而已。為與C語言互操作以及將文字寫入檔案或通過網路套接字傳送出去,Python提供了兩種類似的型別:不可變的bytes和可變的bytearray。如果需要,可直接建立bytes物件(而不是字串),方法是使用字首b:

b’Hello, world!’
b’Hello, world!’
然而,1位元組只能表示256個不同的值,離Unicode標準的要求差很遠。Python bytes字面量只支援ASCII標準中的128個字元,而餘下的128個值必須用轉義序列表示,如\xf0表示十六進位制值0xf0(即240)。
唯一的差別好像在於可用的字母表規模,但實際上並非完全如此。乍一看,好像ASCII和Unicode定義的都是非負整數和字元之間的對映,但存在細微的差別:Unicode碼點是使用整數定義的,而ASCII字元是使用對應的數及其二進位制編碼定義的。這一點好像無關緊要,原因之一是整數0~255和8位二進位制數之間的對映是固定的,幾乎沒有任何機動空間。問題是超過1位元組後,情況就不那麼簡單了:直接將每個碼點表示為相應的二進位制數可能不再可行。這是因為不僅存在位元組順序的問題(即便對整數值進行編碼,也會遇到這樣的問題),而且還可能浪費空間:如果對於每個碼點都使用相同數量的位元組進行編碼,就必須考慮到文字可能包含安那託利亞象形文字或皇家亞蘭字母。有一種Unicode編碼標準是基於這種考慮的,它就是UTF-32(32位統一編碼轉換格式,Unicode Transformation Format 32 bits),但如果你主要處理的是使用網際網路上常見語言書寫的文字,那麼使用這種編碼標準將很浪費空間。
然而,有一種非常巧妙的替代方式:不使用全部32位,而是使用變長編碼,即對於不同的字元,使用不同數量的位元組進行編碼。這種編碼方式主要出自計算機先鋒Kenneth Thompson之手。通過使用這種編碼,可節省佔用的空間,就像摩爾斯碼使用較少的點和短線表示常見的字母,從而減少工作量一樣 ① 。具體地說,進行單位元組編碼時,依然使用ASCII編碼,以便與較舊的系統相容;但對於不在這個範圍內的字元,使用多個位元組(最多為6個)進行編碼。下面來使用ASCII、UTF-8和UTF-32編碼將字串轉換為bytes。

“Hello, world!”.encode(“ASCII”)
b’Hello, world!’

“Hello, world!”.encode(“UTF-8”)
b’Hello, world!’

“Hello, world!”.encode(“UTF-32”)
b’\xff\xfe\x00\x00H\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00,\x00\ x00\x00 \x00\x00\x00w\x00\x00\x00o\x00\x00\x00r\x00\x00\x00l\x00\x00\x00d\x00\x00\x00!\x00\ x00\x00’
從中可知,使用前兩種編碼的結果相同,但使用最後一種編碼的結果長得多。再來看一個示例:

len(“How long is this?”.encode(“UTF-8”))
17

len(“How long is this?”.encode(“UTF-32”))
72
只要字串包含較怪異的字元,ASCII和UTF-8之間的差別便顯現出來了:

“Hællå, wørld!”.encode(“ASCII”)
Traceback (most recent call last):

UnicodeEncodeError: ‘ascii’ codec can’t encode character ‘\xe6’ in position 1: ordinal not in range(128)
斯堪的納維亞字母沒有對應的ASCII編碼。如果必須使用ASCII編碼(這樣的情況肯定會遇到),可向encode提供另一個實參,告訴它如何處理錯誤。這個引數預設為strict,但可將其指定為其他值,以忽略或替換不在ASCII表中的字元。

“Hællå, wørld!”.encode(“ASCII”, “ignore”)
b’Hll, wrld!’

“Hællå, wørld!”.encode(“ASCII”, “replace”)
b’H?ll?, w?rld!’

“Hællå, wørld!”.encode(“ASCII”, “backslashreplace”)
b’H\xe6ll\xe5, w\xf8rld!’

“Hællå, wørld!”.encode(“ASCII”, “xmlcharrefreplace”)
b’Hællå, wørld!’
幾乎在所有情況下,都最好使用UTF-8。事實上,它也是預設使用的編碼。

“Hællå, wørld!”.encode()
b’H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!’
這相比於Hello, world!,編碼結果要長些;但使用UTF-32編碼時,結果一樣長。
可將字串編碼為bytes,同樣也可將bytes解碼為字串。

b’H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!’.decode()
‘Hællå, wørld!’
與前面一樣,預設編碼也是UTF-8。你可指定其他編碼,但如果指定的編碼不正確,將出現錯誤訊息或得到一堆亂碼。bytes物件本身並不知道使用的是哪種編碼,因此你必須負責跟蹤這一點。
可不使用方法encode和decode,而直接建立bytes和str(即字串)物件,如下所示:

bytes(“Hællå, wørld!”, encoding=“utf-8”)
b’H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!’

str(b’H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!’, encoding=“utf-8”)
‘Hællå, wørld!’
這種方法更通用一些,在你不知道類似於字串或bytes的物件屬於哪個類時,使用這種方法也更管用。一個通用規則是,不要做過於嚴格的假設。
編碼和解碼的最重要用途之一是,將文字儲存到磁碟檔案中。然而,Python提供的檔案讀寫機制通常會替你完成這方面的工作!只要檔案使用的是UTF-8編碼,就無需操心編碼和解碼的問題。但如果原本正常的文字變成了亂碼,就說明檔案使用的可能是其他編碼。在這種情況下,對導致這種問題的原因有所瞭解將大有裨益。如果你想更詳細地瞭解Python中的Unicode,請參閱線上文件中有關該主題的HOWTO部分 ① 。
注意 原始碼也將被編碼,且預設使用的也是UTF-8編碼。如果你想使用其他編碼(例如,如果你使用的文字編輯器使用其他編碼來儲存原始碼),可使用特殊的註釋來指定。
-- coding: encoding name --
請將其中的encoding name替換為你要使用的編碼(大小寫都行),如utf-8或latin-1。
最後,Python還提供了bytearray,它是bytes的可變版。從某種意義上說,它就像是可修改的字串——常規字串是不能修改的。然而,bytearray其實是為在幕後使用而設計的,因此作為類字串使用時對使用者並不友好。例如,要替換其中的字元,必須將其指定為0~255的值。因此,要插入字元,必須使用ord獲取其序數值(ordinal value)。

x = bytearray(b"Hello!")
x[1] = ord(b"u")
x
bytearray(b’Hullo!’)