Python 與 Unicode

最近使用 Python 2 處理一些網絡相關的問題,被 Unicode, String 相關的一系列編碼問題搞得一頭霧水。在這裏整理一下相關的概念吧。python

ASCII Unicode UTF8

首先是與 Python 無關的編碼問題。在這裏理清楚這幾個常見的名詞:ASCII, Unicode, UTF8 之間的關係。網絡

咱們知道,爲了使用計算機處理字符,須要將字符編碼爲數字。對於英文字符,128 個數字就夠了,其中最多見的英文字符編碼方案就是 ASCII,該方案規定了使用 7 位二進制數編碼英文字符的方案。數據結構

對於非英文字符,特別是漢語這種有超大字符集的語言,須要須要一個很大的編碼表將字符編碼爲數字。競爭以後如今通用的方案是 Unicode,該方案的目標是覆蓋全部人類語言符號。也就是說,Unicode 定義了一個從任意字符到數字的一一映射關係。函數

對於 ASCII 而言,一個字符(使用 7 位編碼)佔用一個字節(8 位)就夠了,能夠簡單地用這個字節表示一個無符號整數來表明編碼,浪費並不大。可是對於 Unicode 而言,因爲碼錶太大,須要不少字節才能表示一個 Unicode 編碼數字,並且 Unicode 是一個成長中的項目,其碼錶不斷擴充。所以,如何用字節表示 Unicode 編碼數字就成了一個問題,UTF-8 就是解決這個問題的方案之一。該方案是一種變長方案,使用不定長的字節表示一個數字。方案對於包含在 ASCII 碼錶中的字符只使用 1 個字節進行編碼,對於非英語拼音語言符號一般使用 2 個字節編碼,漢字一般使用 3 個字節進行編碼。編碼

若是接觸過信息論,能夠很容易的理解變長編碼是如何作到的。若是讀者沒有接觸過信息論,這裏展現一種簡陋的變長編碼方案,方便讀者理解。
第一個字節老是使用無符號整數表示。當數值位於 [0,254] 中時,這個字節的表明的數值就是編碼的數值。但當數值是 255 時,不表示這個數字是 255,而是表示這個數字加上以後兩位(16 bit) 表明的數以後纔是編碼的數字,因而這個邊長方案能使用 1~3 字節編碼 [0, (2 ^ 16 - 1) + (2 ^ 8 - 1)] 之間的數字。code

總結而言就是,Unicode 是一個字符到數字映射表,而 UTF-8 是數字到字節的編碼方案。ASCII 因爲只使用一個字節,一般不太強調其數字的編碼方案。對象

Python 與 Unicode

接下來是 Python 中對於字符串的處理。unicode

python 2 中的 str 和 unicode

在 Python 2 中,其 str 類型規定了底層的數據結構,是 8 位整數串,也即跟 C 語言中的字符串相似。而 unicode 類型是整數串,並不規定整數的位數或保存方式。unicode.encode() 方法在指定一種編碼方式以後返回一個 str 對象,即爲這個 unicode 字符串在該編碼方式之下的字節表示。字符串

用例子說明這個問題:string

# python 2
us = u"你好"
assert(len(us) == 2)
# us 是整數串,共有兩個整數,表明兩個字符

s = us.encode("utf8")
assert(len(s) == 6)
# 使用 utf8 編碼以後,每一個漢字用 3 個字節表示,共 6 個字節

僅僅是這樣,並不會引發太大的混淆。一個問題是 python 2 的字符串是能夠被初始化爲非英文字符的:

# python 2
s = "你好"
assert(len(s) == 6)

這種狀況下解釋器會自動完成編碼工做,具體編碼方式筆者認爲用戶不該該知道,並且筆者認爲這種用法可能不是一種很好的用法。

更加糟糕的是,python 2 的 print 語句表現有時候會有些神奇:

# python 2
print "你好"
# 獲得 你好
print u"你好"
# 獲得 你好
print ["你好"]
# 獲得 ['\xe4\xbd\xa0\xe5\xa5\xbd']
print [u"你好"]
# 獲得 [u'\u4f60\u597d']

也就是,只有當直接 print str 或者 print unicode 的時候可以正常輸出中文,其餘狀況下, print [str] 會輸出 'str'print [unicode] 會輸出 u'unicode'。這些打印結果看上去會讓人懷疑沒有解碼,其實是 print 語句的行爲而已。

python 3 中的 byte 和 str

在 python 2 中,str 是一種底層數據結構,相似於比特串,但 string 這個英語單詞的意思是字符串,容易引發混淆,如 unicode.encode() 返回值是一個 str,邏輯上並不恰當。

這些問題在 python 3 中獲得解決。python 2 中的 str 類型至關於 python 3 中的 bytes 類型, bytes 這個名字明確的指出這是字節串,而且不指定字節串表明什麼東西。而 python 3 中的 str 相似於 python 2 中的 unicode 類型, 再也不指定底層編碼規則。str.encode() 返回一個 bytes 類型也更加符合邏輯。

用例子說明:

# python 3
s = "你好「
assert(len(s) == 2)

bs = s.encode('utf8')
assert(len(s) == 6)

Python 3 中的 print 函數通常老是能夠正常打印 str,而不是輸出碼值。

相關文章
相關標籤/搜索