Python: 熟悉又陌生的字符編碼

字符編碼是計算機編程中不可迴避的問題,無論你用 Python2 仍是 Python3,亦或是 C++, Java 等,我都以爲很是有必要釐清計算機中的字符編碼概念。本文主要分如下幾個部分介紹:html

  • 基本概念python

  • 常見字符編碼簡介編程

  • Python 的默認編碼網絡

  • Python2 中的字符類型函數

  • UnicodeEncodeError & UnicodeDecodeError 根源網站

基本概念

  • 字符(Character)編碼

在電腦和電信領域中,字符是一個信息單位,它是各類文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等。好比,一個漢字,一個英文字母,一個標點符號等都是一個字符。spa

  • 字符集(Character set)3d

字符集是字符的集合。字符集的種類較多,每一個字符集包含的字符個數也不一樣。好比,常見的字符集有 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 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
  • Python3

Python 3.5.2 (default, Jun 29 2016, 13:43:58)
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'

Python2 中的字符類型

Python2 中有兩種和字符串相關的類型:str 和 unicode,它們的父類是 basestring。其中,str 類型的字符串有多種編碼方式,默認是 ascii,還有 gbk,utf-8 等,unicode 類型的字符串使用 u'...' 的形式來表示,下面的圖展現了 str 和 unicode 之間的關係:

sm.ms

兩種字符串的相互轉換歸納以下:

  • 把 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 (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not 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 (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not 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 your name: 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 (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can'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 類型再運算。

參考資料

相關文章
相關標籤/搜索