在人機交互之字符編碼 一文中對字符編碼進行了詳細的討論,並經過一些簡單的小程序驗證了咱們對於字符編碼的認識。但僅瞭解這篇文章的內容,並不能幫咱們在平常編程中躲過一些字符編碼相關的坑,Stackoverflow 上就有大量編碼相關的問題,好比 1,2,3。html
本文首先嚐試對編碼、解碼進行一個宏觀、直觀的解讀,而後詳細來解釋 python2 中的str和unicode,並對常見的UnicodeEncodeError 和 UnicodeDecodeError 異常進行剖析。python
如何去理解編碼、解碼?舉個例子,Alice同窗剛加入了機器學習這門課,想給同班的Bob同窗打個招呼。可是做爲人,Alice不能經過意念和Bob交流,必須經過某種方式,好比手語、聲音、文字等來表達本身的想法。若是Alice選擇用文字,那麼他可能會寫下這麼一段文字:My name is: boot …… 來學機器學習嘍
,寫文字這個過程其實就是編碼,通過編碼後的文字才能給Bob看。Bob收到Alice的文字後,就會用本身對文字的認知來解讀Alice傳達的含義,這個過程其實就是解碼。固然,若是Bob不懂中文,那麼就沒法理解Alice的最後一句了,若是Bob不識字,就徹底不知道Alice想表達什麼了。shell
上面的例子只是爲了方便咱們理解編碼、解碼這個抽象的概念,如今來看看對於計算機程序來講,如何去理解字符的編碼、解碼過程。咱們知道絕大多數程序都是讀取數據,作一些操做,而後輸出數據。好比當咱們打開一個文本文件時,就會從硬盤讀取文件中的數據,接着咱們輸入了新的數據,點擊保存後,文本程序會將更新後的內容輸出到硬盤。程序讀取數據就至關於Bob讀文字,必須進行一個解碼的過程,解碼後的數據才能讓咱們進行各類操做。同理,保存到硬盤時,也須要對數據進行編碼。數據庫
下圖方框 A 表明一個輸出數據的程序,方框 B 表明一個讀取數據的程序。固然這裏的程序只是一個概念,表示一個處理數據的邏輯單元,能夠是一個進程、一個函數甚至一個語句等。A 和 B 也能夠是同一個程序,先解碼外部獲取的數據,內部操做後,再進行某種編碼。編程
值得注意的是,有的編碼方案不必定能表示某些信息,這時編碼就會失敗,好比 ASCII 就不能用來表示中文。固然,若是以錯誤的方式去解讀某段內容,解碼也會失敗,好比用 ASCII 來解讀包含 UTF-8的信息。至於什麼是 ASCII,UTF-8等,在人機交互之字符編碼 中有詳細的說明,這裏再也不贅述。下面結合具體的例子,來看看編碼、解碼的細節問題。小程序
在程序設計中,字符串通常是指一連串的字符,好比hello world!
、你好
或者もしもし
(日語)等等。各類語言對於字符串的支持各不相同,Python 2 中字符串的設計頗不合理,致使新手常常會出現各類問題,相似下面的提示信息相信不少人都遇到過(UnicodeEncodeError
或者 UnicodeDecodeError
):數組
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)
下面咱們一塊兒來解決這個疑難雜症。首先須要搞清楚python中的兩個類型:<type 'str'>
和 <type 'unicode'>
,文檔中關於這兩個類型的說明其實挺含糊的:網絡
There are seven sequence types: strings, Unicode strings, lists, ...機器學習
String literals are written in single or double quotes: 'xyzzy', "frobozz". Unicode strings are much like strings, but are specified in the syntax using a preceding 'u' character: u'abc', u"def".函數
上面並無給出什麼有用的信息,不過好在這篇文章講的特別好,簡單來講:
str:是字節串(container for bytes)
,由 Unicode 通過編碼(encode)後的字節組成的。
unicode:真正意義上的字符串
,其中的每一個字符用 Unicode 中對應的 Code Point 表示。
翻譯成人話就是,unicode 有點相似於前面 Alice 打招呼傳遞的想法,而 str 則是寫下來的文字(或者是說出來的聲音,甚至能夠是手語)。咱們能夠用 GBK,UTF-8 等編碼方案將 Unicode 類型轉換爲 str 類型,相似於用語言、文字或者手語來表達想法。
爲了完全理解字符編碼、解碼,下面要用 python 交互界面進行一些小實驗來加深咱們的理解(下面全部的交互代碼均在 Linux 平臺下)。在這以前,咱們先來看下面交互代碼:
>>> demo = 'Test 試試' >>> demo 'Test \xe8\xaf\x95\xe8\xaf\x95'
當咱們只輸入標識符 demo 時,終端返回了 demo 的內容。這裏返回的內容是怎麼獲得呢?答案是經過 repr() 函數 得到。文檔中對於 repr 函數解釋以下:
Return a string containing a printable representation of an object.
因此,咱們能夠在源文件中用下面的代碼,來獲取和上面終端同樣的輸出。
#! /usr/bin/env python # -*- coding: UTF-8 -*- demo = 'Test 試試' print repr(demo) # 'Test \xe8\xaf\x95\xe8\xaf\x95'
對於字符串來講,repr()
的返回值很好地說明了其在python內部的表示方式。經過 repr 的返回值,咱們能夠真切體會到前面提到的兩點:
str:其實是字節串
unicode:真正意義上的字符串
下面分別來看看這兩個類型。
unicode 是真正意義上的字符串,爲了理解這句話,先看下面的一段代碼:
>>> unicode_str = u'Welcome to 廣州' # ''前面的 u 表示這是一個 unicode 字符串 >>> unicode_str, type(unicode_str) # repr(unicode_str) (u'Welcome to \u5e7f\u5dde', <type 'unicode'>)
repr 返回的 Welcome to u5e7fu5dde
說明了unicode_str存儲的內容,其中兩個u
後面的數字分別對應了廣、州
在unicode中的code point:
5e7f
對應廣
字;
5dde
對應州
字;
英文字母也有對應的code point,它的值等於ASCII值,不過repr並無直接輸出。咱們能夠在站長工具中查看全部字符對應的code point。也能夠用 python 的內置函數 ord
查看字符的 code point,以下所示(調用了 format 將code point轉換爲十六進制):
>>> '{:04x}'.format(ord(u'廣')) '5e7f' >>> '{:04x}'.format(ord(u'W')) '0057'
總結一下,咱們能夠將 <type 'unicode'>
看做是一系列字符組成的數組,數組的每一項是一個code point,用來表示相應位置的字符。因此對於 unicode 來講,其長度等於它包含的字符(a
和 廣
都是一個字符)的數目。
>>> len(unicode_str) 13 >>> unicode_str[0], unicode_str[12], unicode_str[-1] (u'W', u'\u5dde', u'\u5dde')
str 是字節串(container for bytes),爲了理解這句話,先來看下面的一段代碼:
>>> str_str = 'Welcome to 廣州' # 這是一個 str >>> str_str, type(str_str) ('Welcome to \xe5\xb9\xbf\xe5\xb7\x9e', <type 'str'>)
python中 xhh
(h爲16進制數字)表示一個字節,輸出中的xe5xb9xbfxe5xb7x9e
就是所謂的字節串,它對應了廣州
。實際上 str_str 中的英文字母也是保存爲字節串的,不過 repr 並無以 x
的形式返回。爲了驗證上面輸出內容確實是字節串,咱們用python提供的 bytearray 函數將相同內容的 unicode字符串用 UTF-8 編碼爲字節數組,以下所示:
>>> unicode_str = u'Welcome to 廣州' >>> bytearray(unicode_str, 'UTF-8') bytearray(b'Welcome to \xe5\xb9\xbf\xe5\xb7\x9e') >>> list(bytearray(unicode_str, 'UTF-8')) # 字節數組,每一項爲一個字節; [87, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 229, 185, 191, 229, 183, 158] >>> print r"\x" + r"\x".join(["%02x" % c for c in list(bytearray(unicode_str, 'UTF-8'))]) # 轉換爲 \xhh 的形式 \x57\x65\x6c\x63\x6f\x6d\x65\x20\x74\x6f\x20\xe5\xb9\xbf\xe5\xb7\x9e
可見,上面的 str_str 是 unicode_str 通過 UTF-8 編碼 後的字節串。這裏透漏了一個十分重要的信息,<type 'str'>類型隱含有某種編碼方式,正是這種隱式編碼(implicit encoding)的存在致使了許多問題的出現(後面詳細說明)。值得注意的是,str類型字節串的隱式編碼不必定都是'UTF-8',前面示例程序都是在 OS X 平臺下的終端,因此隱式編碼是 UTF-8。對於 Windows 而言,若是語言設置爲簡體中文,那麼交互界面輸出以下:
# Win 平臺下,系統語言爲簡體中文 >>> str_str = 'Welcome to 廣州' >>> str_str, type(str_str) ('Welcome to \xb9\xe3\xd6\xdd', <type 'str'>)
這裏str_str的隱式編碼是cp936,能夠用 bytearray(unicode_str, 'cp936')
來驗證這點。終端下,str類型的隱式編碼由系統 locale 決定,能夠採用下面方式查看:
# Unix or Linux >>> import locale >>> locale.getdefaultlocale() ('zh_CN', 'UTF-8') ... # 簡體中文 Windows >>> locale.getdefaultlocale() ('zh_CN', 'cp936')
總結一下,咱們能夠將 <type 'str'>
看做是unicode字符串通過某種編碼後的字節組成的數組。數組的每一項是一個字節,用 xhh
來表示。因此對於 str 字符串來講,其長度等於編碼後字節的長度。
>>> len(str_str) 17 >>> str_str[0], str_str[-1] ('W', '\x9e') # 其實是('\x57', '\x9e')
Python 2.x 中爲上面兩種類型的字符串都提供了 encode 和 decode 方法,原型以下:
str.decode([encoding[, errors]])
str.encode([encoding[, errors]])
利用上面的兩個函數,能夠實現 str 和 unicode 類型之間的相互轉換,以下圖所示:
上圖中綠色線段標示的即爲咱們經常使用的轉換方法,紅色標示的轉換在 python 2.x 中是合法的,不過沒有什麼意義,一般會拋出錯誤(能夠參見 What is the difference between encode/decode?)。下面是兩種類型之間的轉換示例:
# decode: <type 'str'> 到 <type 'unicode'>的轉換 >>> enc = str_str.decode('utf-8') >>> enc, type(enc) (u'Welcome to \u5e7f\u5dde', <type 'unicode'>) # encode: <type 'unicode'> 到 <type 'str'> 的轉換 >>> dec = unicode_str.encode('utf-8') >>> dec, type(dec) ('Welcome to \xe5\xb9\xbf\xe5\xb7\x9e', <type 'str'>)
上面代碼中經過encode將unicode類型編碼爲str類型,經過 decode 將str類型解碼爲unicode類型。固然,編碼、解碼的過程並不老是一路順風的,一般會出現各類錯誤。
Python 中常常會遇到 UnicodeEncodeError 和 UnicodeDecodeError,怎麼產生的呢? 以下代碼所示:
>>> u'Hello 廣州'.encode('ascii') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-7: ordinal not in range(128) >>> 'Hello 廣州'.decode('ascii') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 6: ordinal not in range(128)
當咱們用 ascii 去編碼帶有中文的unicode字符串時,發生了UnicodeEncodeError,當咱們用 ascii 去解碼有中文的str字節串時,發生了UnicodeDecodeError。咱們知道,ascii 只包含 127 個字符,根本沒法表示中文。因此,讓 ascii 來編碼、解碼中文,就超出了其能力範圍。這就像你對一個不懂中文的老外說中文,他根本無法聽懂。簡單來講,全部的編碼、解碼錯誤都是因爲所選的編碼、解碼方式沒法表示某些字符形成的。
有時候咱們就是想用 ascii 去編碼一段夾雜中文的str字節串,並不但願拋出異常。那麼能夠經過 errors 參數來指定當沒法編碼某個字符時的處理方式,經常使用的處理方式有 "strict","ignore"和"replace"。改動後的程序以下:
>>> u'Hello 廣州'.encode('ascii', 'replace') 'Hello ??' >>> u'Hello 廣州'.encode('ascii', 'ignore') 'Hello '
str和unicode類型均可以用來表示字符串,爲了方便它們之間進行操做,python並不要求在操做以前統一類型,因此下面的代碼是合法的,而且能獲得正確的輸出:
>>> new_str = u'Welcome to ' + 'GuangZhou' >>> new_str, type(new_str) (u'Welcome to GuangZhou', <type 'unicode'>)
由於str類型是隱含有某種編碼方式的字節碼,因此python內部將其解碼爲unicode後,再和unicode類型進行 + 操做
,最後返回的結果也是unicode類型。
第2步的解碼過程是在幕後悄悄發生的,默認採用ascii來進行解碼,能夠經過 sys.getdefaultencoding()
來獲取默認編碼方式。Python 之因此採用 ascii,是由於 ascii 是最先的編碼方式,是許多編碼方式的子集。
不過正是這個不可見的解碼過程,有時候會致使出乎意料的解碼錯誤,考慮下面的代碼:
>>> u'Welcome to' + '廣州' Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)
上面在字符串的+操做時,python 偷偷對'廣州'用 ascii 作解碼操做,因此拋出了UnicodeDecodeError異常。其實上面操做等同於 u'Welcome to' + '廣州'.decode('ascii')
,你會發現這句代碼拋出的異常和上面的如出一轍。
Python 不僅偷偷地用 ascii 來解碼str類型的字節串,有時還會偷偷用ascii來編碼unicode類型。若是函數或類等對象接收的是 str 類型的字符串,但傳進去的是unicode,python2 就會使用 ascii 將其編碼成str類型再作運算。
以raw_input爲例,咱們能夠給 raw_input 函數提供 prompt 參數,做爲輸入提示內容。這裏若是 prompt 是 unicode 類型,python會先用ascii對其進行編碼,因此下面代碼會拋出UnicodeEncodeError異常:
>>> a = raw_input(u'請輸入內容: ') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)
上面操做徹底等同於 a = raw_input(u'請輸入內容: '.encode('ascii'))
,你會發現它們拋出的異常徹底同樣。此外,若是嘗試將unicode字符串重定向輸出到文本中,也可能會拋出UnicodeEncodeError異常。
$ cat a.py demo = u'Test 試試' print demo $ python a.py > output Traceback (most recent call last): File "a.py", line 5, in <module> print demo UnicodeEncodeError: 'ascii' codec can't encode characters in position 5-6: ordinal not in range(128)
固然,若是直接在終端進行輸出,則不會拋出異常。由於python會使用控制檯的默認編碼,而不是 ascii。
總結下本文的內容:
str能夠看做是unicode字符串通過某種編碼後的字節組成的數組
unicode是真正意義上的字符串
經過 encode 能夠將unicode類型編碼爲str類型
經過 decode 能夠將str類型解碼爲unicode類型
python 會隱式地進行編碼、解碼,默認採用 ascii
全部的編碼、解碼錯誤都是因爲所選的編碼、解碼方式沒法表示某些字符形成的
若是你明白了上面每句話的含義,那麼應該能解決大部分編、解碼引發的問題了。固然,本篇文章其實並不能幫你徹底避免python編碼中的坑(坑太多)。還有許多問題在這裏並無說明:
讀取、寫入文件時的編碼問題:
數據庫的讀寫
網絡數據操做
源文件編碼格式的指定
有空再詳細談談上面列出的坑。
本文由selfboot 發表於我的博客,採用署名-非商業性使用-相同方式共享 3.0 中國大陸許可協議。
非商業轉載請註明做者及出處。商業轉載請聯繫做者本人
本文標題爲:Python2.x 字符編碼終極指南
本文連接爲:http://selfboot.cn/2016/12/28...
Pragmatic Unicode
Unicode In Python, Completely Demystified
Solving Unicode Problems in Python 2.7
Unicode HOWTO
Wiki:PrintFails
Unicode and Character Sets
What is the purpose of __str__ and __repr__ in Python?
What does a leading \x mean in a Python string \xaa
Python: 熟悉又陌生的字符編碼
PYTHON-進階-編碼處理小結
五分鐘打敗 Python 字符編碼
python 字符編碼與解碼