Python: 熟悉又陌生的字符編碼
基本概念
- 字符(Character)
在電腦和電信領域中, 字符是一個信息單位,它是各種文字和符號的總稱 ,包括各國家文字、標點符號、圖形符號、數字等。比如,一個漢字,一個英文字母,一個標點符號等都是一個字符。
- 字符集(Character set)
字符集是字符的集合。字符集的種類較多,每個字符集包含的字符個數也不同。比如,常見的字符集有 ASCII 字符集、GB2312 字符集、Unicode 字符集等,其中,ASCII 字符集共有 128 個字符,包含可顯示字符(比如英文大小寫字符、阿拉伯數字)和控制字符(比如空格鍵、回車鍵);GB2312 字符集是中國國家標準的簡體中文字符集,包含簡化漢字、一般符號、數字等;Unicode 字符集則包含了世界各國語言中使用到的所有字符,
- 字符編碼(Character encoding)
字符編碼,是指對于字符集中的字符,將其編碼為特定的二進制數,以便計算機處理。常見的字符編碼有 ASCII 編碼,UTF-8 編碼,GBK 編碼等。一般而言, 字符集 和 字符編碼 往往被認為是同義的概念,比如,對于字符集 ASCII,它除了有「字符的集合」這層含義外,同時也包含了「編碼」的含義,也就是說, ASCII 既表示了字符集也表示了對應的字符編碼 。
下面我們用一個表格做下總結:
概念 | 概念描述 | 舉例 |
---|---|---|
字符 | 一個信息單位,各種文字和符號的總稱 | ‘中’, ‘a’, ‘1’, ‘$’, ‘¥’, … |
字符集 | 字符的集合 | ASCII 字符集, GB2312 字符集, Unicode 字符集 |
字符編碼 | 將字符集中的字符,編碼為特定的二進制數 | ASCII 編碼,GB2312 編碼,Unicode 編碼 |
字節 | 計算機中存儲數據的單元,一個 8 位(bit)的二進制數 | 0x01, 0x45, … |
常見字符編碼簡介
常見的字符編碼有 ASCII 編碼,GBK 編碼,Unicode 編碼和 UTF-8 編碼等等。這里,我們主要介紹 ASCII、Unicode 和 UTF-8。
ASCII
計算機是在美國誕生的,人家用的是英語,而在英語的世界里,不過就是英文字母,數字和一些普通符號的組合而已。
在 20 世紀 60 年代,美國制定了一套字符編碼方案,規定了英文字母,數字和一些普通符號跟二進制的轉換關系,被稱為 ASCII (American Standard Code for Information Interchange,美國信息互換標準編碼) 碼。
比如,大寫英文字母 A 的二進制表示是 01000001(十進制 65),小寫英文字母 a 的二進制表示是 01100001 (十進制 97),空格 SPACE 的二進制表示是 00100000(十進制 32)。
Unicode
ASCII 碼只規定了 128 個字符的編碼,這在美國是夠用的。可是,計算機后來傳到了歐洲,亞洲,乃至世界各地,而世界各國的語言幾乎是完全不一樣的,用 ASCII 碼來表示其他語言是遠遠不夠的,所以,不同的國家和地區又制定了自己的編碼方案,比如中國大陸的 GB2312 編碼 和 GBK 編碼等,日本的 Shift_JIS 編碼等等。
雖然各個國家和地區可以制定自己的編碼方案,但不同國家和地區的計算機在數據傳輸的過程中就會出現各種各樣的亂碼(mojibake),這無疑是個災難。
怎么辦?想法也很簡單,就是將全世界所有的語言統一成一套編碼方案,這套編碼方案就叫 Unicode, 它為每種語言的每個字符設定了獨一無二的二進制編碼 ,這樣就可以跨語言,跨平臺進行文本處理了,是不是很棒!
Unicode 1.0 版誕生于 1991 年 10 月,至今它仍在不斷增修,每個新版本都會加入更多新的字符,目前最新的版本為 2016 年 6 月 21 日公布的 9.0.0。
Unicode 標準使用十六進制數字,而且在數字前面加上前綴 U+ ,比如,大寫字母「A」的 unicode 編碼為 U+0041 ,漢字「嚴」的 unicode 編碼為 U+4E25 。更多的符號對應表,可以查詢 unicode.org ,或者專門的 漢字對應表 。
UTF-8
Unicode 看起來已經很完美了,實現了大一統。但是,Unicode 卻存在一個很大的問題:資源浪費。
為什么這么說呢?原來,Unicode 為了能表示世界各國所有文字,一開始用兩個字節,后來發現兩個字節不夠用,又用了四個字節。比如,漢字「嚴」的 unicode 編碼是十六進制數 4E25 ,轉換成二進制有十五位,即 100111000100101,因此至少需要兩個字節才能表示這個漢字,但是對于其他的字符,就可能需要三個或四個字節,甚至更多。
這時,問題就來了,如果以前的 ASCII 字符集也用這種方式來表示,那豈不是很浪費存儲空間。比如,大寫字母「A」的二進制編碼為 01000001,它只需要一個字節就夠了,如果 unicode 統一使用三個字節或四個字節來表示字符,那「A」的二進制編碼的前面幾個字節就都是 0 ,這是很浪費存儲空間的。
為了解決這個問題,在 Unicode 的基礎上,人們實現了 UTF-16, UTF-32 和 UTF-8。下面只說一下 UTF-8。
UTF-8 (8-bit Unicode Transformation Format) 是一種針對 Unicode 的可變長度字符編碼,它使用一到四個字節來表示字符,例如,ASCII 字符繼續使用一個字節編碼,阿拉伯文、希臘文等使用兩個字節編碼,常用漢字使用三個字節編碼,等等。
因此,我們說, UTF-8 是 Unicode 的實現方式之一 ,其他實現方式還包括 UTF-16(字符用兩個或四個字節表示)和 UTF-32(字符用四個字節表示)。
Python 的默認編碼
Python2 的默認編碼是 ascii,Python3 的默認編碼是 utf-8,可以通過下面的方式獲取:
-
Python2
Python 2.7.11 (default, Feb 24 2016, 10:48:05) [GCC 4.2.1 CompatibleAppleLLVM 7.0.2 (clang-700.1.81)] ondarwin Type "help", "copyright", "credits" or "license" for moreinformation. >>> import sys >>> sys.getdefaultencoding() 'ascii'
-
Python3
Python 3.5.2 (default, Jun 29 2016, 13:43:58) [GCC 4.2.1 CompatibleAppleLLVM 7.3.0 (clang-703.0.31)] ondarwin Type "help", "copyright", "credits" or "license" for moreinformation. >>> import sys >>> sys.getdefaultencoding() 'utf-8'
Python2 中的字符類型
Python2 中有兩種和字符串相關的類型:str 和 unicode,它們的父類是 basestring。其中,str 類型的字符串有多種編碼方式,默認是 ascii,還有 gbk,utf-8 等,unicode 類型的字符串使用 u'...' 的形式來表示,下面的圖展示了 str 和 unicode 之間的關系:
兩種字符串的相互轉換概括如下:
-
把 UTF-8 編碼表示的字符串 ‘xxx’ 轉換為 Unicode 字符串 u’xxx’ 用 decode('utf-8') 方法:
>>> '中文'.decode('utf-8') u'\u4e2d\u6587'
-
把 u’xxx’ 轉換為 UTF-8 編碼的 ‘xxx’ 用 encode('utf-8') 方法:
>>> u'中文'.encode('utf-8') '\xe4\xb8\xad\xe6\x96\x87'
UnicodeEncodeError & UnicodeDecodeError 根源
用 Python2 編寫程序的時候經常會遇到 UnicodeEncodeError 和 UnicodeDecodeError,它們出現的根源就是 如果代碼里面混合使用了 str 類型和 unicode 類型的字符串,Python 會默認使用 ascii 編碼嘗試對 unicode 類型的字符串編碼 (encode),或對 str 類型的字符串解碼 (decode),這時就很可能出現上述錯誤 。
下面有兩個常見的場景,我們最好牢牢記住:
- 在進行同時包含 str 類型和 unicode 類型的字符串操作時,Python2 一律都把 str 解碼(decode)成 unicode 再運算,這時就很容易出現 UnicodeDecodeError。
讓我們看看例子:
>>> s = '你好' # str 類型, utf-8 編碼
>>> u = u'世界' # unicode 類型
>>> s + u # 會進行隱式轉換,即 s.decode('ascii') + u
Traceback (mostrecentcalllast):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codeccan't decodebyte 0xe4 in position 0: ordinalnot in range(128)
為了避免出錯,我們就需要顯示指定使用 ‘utf-8’ 進行解碼,如下:
>>> s = '你好' # str 類型,utf-8 編碼
>>> u = u'世界'
>>>
>>> s.decode('utf-8') + u # 顯示指定 'utf-8' 進行轉換
u'\u4f60\u597d\u4e16\u754c' # 注意這不是錯誤,這是 unicode 字符串
- 如果函數或類等對象接收的是 str 類型的字符串,但你傳的是 unicode,Python2 會默認使用 ascii 將其編碼成 str 類型再運算,這時就很容易出現 UnicodeEncodeError。
讓我們看看例子:
>>> u_str = u'你好'
>>> str(u_str)
Traceback (mostrecentcalllast):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codeccan't encodecharactersin position 0-1: ordinalnot in range(128)
在上面的代碼中,u_str 是一個 unicode 類型的字符串,由于 str() 的參數只能是 str 類型,此時 Python 會試圖使用 ascii 將其編碼成 ascii,也就是:
u_str.encode('ascii') // u_str 是 unicode 字符串
上面將 unicode 類型的中文使用 ascii 編碼轉,肯定會出錯。
再看一個使用 raw_input 的例子,注意 raw_input 只接收 str 類型的字符串:
>>> name = raw_input('input your name: ')
input yourname: ethan
>>> name
'ethan'
>>> name = raw_input('輸入你的姓名:')
輸入你的姓名: 小明
>>> name
'\xe5\xb0\x8f\xe6\x98\x8e'
>>> type(name)
<type 'str'>
>>> name = raw_input(u'輸入你的姓名: ') # 會試圖使用 u'輸入你的姓名'.encode('ascii')
Traceback (mostrecentcalllast):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codeccan't encode characters in position 0-5: ordinal not in range(128)
>>> name = raw_input(u'輸入你的姓名: '.encode('utf-8')) #可以,但此時 name 不是 unicode 類型
輸入你的姓名: 小明
>>> name
'\xe5\xb0\x8f\xe6\x98\x8e'
>>> type(name)
<type 'str'>
>>> name = raw_input(u'輸入你的姓名: '.encode('utf-8')).decode('utf-8') # 推薦
輸入你的姓名: 小明
>>> name
u'\u5c0f\u660e'
>>> type(name)
<type 'unicode'>
再看一個重定向的例子:
hello = u'你好'
print hello
將上面的代碼保存到文件 hello.py ,在終端執行 python hello.py 可以正常打印,但是如果將其重定向到文件 python hello.py > result 會發現 UnicodeEncodeError 。
這是因為:輸出到控制臺時,print 使用的是控制臺的默認編碼,而重定向到文件時,print 就不知道使用什么編碼了,于是就使用了默認編碼 ascii 導致出現編碼錯誤。
應該改成如下:
hello = u'你好'
print hello.encode('utf-8')
這樣執行 python hello.py > result 就沒有問題。
小結
- UTF-8 是一種針對 Unicode 的可變長度字符編碼,它是 Unicode 的實現方式之一。
- Unicode 字符集有多種編碼標準,比如 UTF-8, UTF-7, UTF-16。
- 在進行同時包含 str 類型和 unicode 類型的字符串操作時,Python2 一律都把 str 解碼(decode)成 unicode 再運算。
- 如果函數或類等對象接收的是 str 類型的字符串,但你傳的是 unicode,Python2 會默認使用 ascii 將其編碼成 str 類型再運算。
參考資料
- 字符 – 維基百科,自由的百科全書
- UTF-8 – 維基百科,自由的百科全書
- 字符,字節和編碼 – Characters, Bytes And Encoding
- 字符編碼筆記:ASCII,Unicode和UTF-8 – 阮一峰的網絡日志
- 字符串和編碼 – 廖雪峰的官方網站
- python – Dangers of sys.setdefaultencoding(‘utf-8’) – Stack Overflow
來自:http://python.jobbole.com/87120/