最近使用 Python 2 處理一些網絡相關的問題,被 Unicode, String 相關的一系列編碼問題搞得一頭霧水。在這裏整理一下相關的概念吧。python
首先是與 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 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 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
,而不是輸出碼值。