1. 程式人生 > >Python 2.x 字元編碼終極指南

Python 2.x 字元編碼終極指南

在人機互動之字元編碼 一文中對字元編碼進行了詳細的討論,並通過一些簡單的小程式驗證了我們對於字元編碼的認識。但僅瞭解這篇文章的內容,並不能幫我們在日常程式設計中躲過一些字元編碼相關的坑,Stackoverflow 上就有大量編碼相關的問題,比如 123

圖1. 錯誤的編解碼

本文首先嚐試對編碼、解碼進行一個巨集觀、直觀的解讀,然後詳細來解釋 python2 中的str和unicode,並對常見的UnicodeEncodeError 和 UnicodeDecodeError 異常進行剖析。

如何理解編、解碼?

如何去理解編碼、解碼?舉個例子,Alice同學剛加入了機器學習這門課,想給同班的Bob同學打個招呼。但是作為人,Alice不能通過意念和Bob交流,必須通過某種方式,比如手語、聲音、文字等來表達自己的想法。如果Alice選擇用文字,那麼他可能會寫下這麼一段文字:My name is: boot …… 來學機器學習嘍

,寫文字這個過程其實就是編碼,經過編碼後的文字才能給Bob看。Bob收到Alice的文字後,就會用自己對文字的認知來解讀Alice傳達的含義,這個過程其實就是解碼。當然,如果Bob不懂中文,那麼就無法理解Alice的最後一句了,如果Bob不識字,就完全不知道Alice想表達什麼了。

上面的例子只是為了方便我們理解編碼、解碼這個抽象的概念,現在來看看對於計算機程式來說,如何去理解字元的編碼、解碼過程。我們知道絕大多數程式都是讀取資料,做一些操作,然後輸出資料。比如當我們開啟一個文字檔案時,就會從硬碟讀取檔案中的資料,接著我們輸入了新的資料,點選儲存後,文字程式會將更新後的內容輸出到硬碟。程式讀取資料就相當於Bob讀文字,必須進行一個解碼的過程,解碼後的資料才能讓我們進行各種操作。同理,儲存到硬碟時,也需要對資料進行編碼。

下圖方框 A 代表一個輸出資料的程式,方框 B 代表一個讀取資料的程式。當然這裡的程式只是一個概念,表示一個處理資料的邏輯單元,可以是一個程序、一個函式甚至一個語句等。A 和 B 也可以是同一個程式,先解碼外部獲取的資料,內部操作後,再進行某種編碼。

圖2. 編碼、解碼的過程

值得注意的是,有的編碼方案不一定能表示某些資訊,這時編碼就會失敗,比如 ASCII 就不能用來表示中文。當然,如果以錯誤的方式去解讀某段內容,解碼也會失敗,比如用 ASCII 來解讀包含 UTF-8的資訊。至於什麼是 ASCII,UTF-8等,在人機互動之字元編碼 中有詳細的說明,這裡不再贅述。下面結合具體的例子,來看看編碼、解碼的細節問題。

python2.x 中的字串

在程式設計中,字串一般是指一連串的字元,比如hello world!你好或者もしもし(日語)等等。各種語言對於字串的支援各不相同,Python 2 中字串的設計頗不合理,導致新手經常會出現各種問題,類似下面的提示資訊相信很多人都遇到過(UnicodeEncodeError 或者 UnicodeDecodeError):

Python
123 Traceback(most recent call last):File"<stdin>",line1,in<module>UnicodeEncodeError:'ascii'codec can'tencode characters inposition0-1:ordinal notinrange(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 型別,類似於用語言、文字或者手語來表達想法。

    repr 與終端互動

    為了徹底理解字元編碼、解碼,下面要用 python 互動介面進行一些小實驗來加深我們的理解(下面所有的互動程式碼均在 Linux 平臺下)。在這之前,我們先來看下面互動程式碼:

    123 >>>demo='Test 試試'>>>demo'Test \xe8\xaf\x95\xe8\xaf\x95'

    當我們只輸入識別符號 demo 時,終端返回了 demo 的內容。這裡返回的內容是怎麼得到呢?答案是通過 repr() 函式 獲得。文件中對於 repr 函式解釋如下:

    > Return a string containing a printable representation of an object.

    所以,我們可以在原始檔中用下面的程式碼,來獲取和上面終端一樣的輸出。

    12345 #! /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 是真正意義上的字串,為了理解這句話,先看下面的一段程式碼:

    123 >>>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 \u5e7f\u5dde 說明了unicode_str儲存的內容,其中兩個\u後面的數字分別對應了廣、州在unicode中的code point:

  • 5e7f 對應字;
  • 5dde 對應字;英文字母也有對應的code point,它的值等於ASCII值,不過repr並沒有直接輸出。我們可以在站長工具中檢視所有字元對應的code point。也可以用 python 的內建函式 ord 檢視字元的 code point,如下所示(呼叫了 format 將code point轉換為十六進位制):
    1234 >>>'{:04x}'.format(ord(u'廣'))'5e7f'>>>'{:04x}'.format(ord(u'W'))'0057'

    總結一下,我們可以將 &amp;lt;type 'unicode'&amp;gt; 看作是一系列字元組成的陣列,陣列的每一項是一個code point,用來表示相應位置的字元。所以對於 unicode 來說,其長度等於它包含的字元(a 都是一個字元)的數目。

    1234 >>>len(unicode_str)13>>>unicode_str[0],unicode_str[12],unicode_str[-1](u'W',u'\u5dde',u'\u5dde')

    str 型別

    str 是位元組串(container for bytes),為了理解這句話,先來看下面的一段程式碼:

    123 >>>str_str='Welcome to 廣州'# 這是一個 str>>>str_str,type(str_str)('Welcome to \xe5\xb9\xbf\xe5\xb7\x9e',<type'str'>)

    python中 \xhh(h為16進位制數字)表示一個位元組,輸出中的\xe5\xb9\xbf\xe5\xb7\x9e 就是所謂的位元組串,它對應了廣州。實際上 str_str 中的英文字母也是儲存為位元組串的,不過 repr 並沒有以 \x 的形式返回。為了驗證上面輸出內容確實是位元組串,我們用python提供的 bytearray 函式將相同內容的 unicode字串用 UTF-8 編碼為位元組陣列,如下所示:

    123456789 >>>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]>>>printr"\x"+r"\x".join(["%02x"%cforcinlist(bytearray(unicode_str,'UTF-8'))])# 轉換為 \xhh 的形式\x57\x65\x6c\x63\x6f\x6d\x65\x20\x74\x6f\x20\xe5\xb9\xbf\xe5\xb7\x9e

    可見,上面的 strstr 是 unicode_str 經過 UTF-8 編碼 後的位元組串。這裡透漏了一個十分重要的資訊,str型別隱含有某種編碼方式,正是這種隱式編碼(_implicit encoding)的存在導致了許多問題的出現(後面詳細說明)。值得注意的是,str型別位元組串的隱式編碼不一定都是’UTF-8’,前面示例程式都是在 OS X 平臺下的終端,所以隱式編碼是 UTF-8。對於 Windows 而言,如果語言設定為簡體中文,那麼互動介面輸出如下:

    1234 # 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 決定,可以採用下面方式檢視:

    12345678 # Unix or Linux>>>import locale>>>locale.getdefaultlocale()('zh_CN','UTF-8')...# 簡體中文 Windows>>>locale.getdefaultlocale()('zh_CN','cp936')

    總結一下,我們可以將 &amp;lt;type 'str'&amp;gt; 看作是unicode字串經過某種編碼後的位元組組成的陣列。陣列的每一項是一個位元組,用 \xhh 來表示。所以對於 str 字串來說,其長度等於編碼後位元組的長度。

    1234 >>>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 型別之間的相互轉換,如下圖所示:

    圖3. 型別間相互轉換

    上圖中綠色線段標示的即為我們常用的轉換方法,紅色標示的轉換在 python 2.x 中是合法的,不過沒有什麼意義,通常會丟擲錯誤(可以參見 What is the difference between encode/decode?)。下面是兩種型別之間的轉換示例:

    123456789 # 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,怎麼產生的呢? 如下程式碼所示:

    123456789 >>>u'Hello 廣州'.encode('ascii')Traceback(most recent call last):File"<stdin>",line1,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'tdecode byte0xe5inposition6:ordinal notinrange(128)

    當我們用 ascii 去編碼帶有中文的unicode字串時,發生了UnicodeEncodeError,當我們用 ascii 去解碼有中文的str位元組串時,發生了UnicodeDecodeError。我們知道,ascii 只包含 127 個字元,根本無法表示中文。所以,讓 ascii 來編碼、解碼中文,就超出了其能力範圍。這就像你對一個不懂中文的老外說中文,他根本沒法聽懂。簡單來說,所有的編碼、解碼錯誤都是由於所選的編碼、解碼方式無法表示某些字元造成的

    有時候我們就是想用 ascii 去編碼一段夾雜中文的str位元組串,並不希望丟擲異常。那麼可以通過 errors 引數來指定當無法編碼某個字元時的處理方式,常用的處理方式有 “strict”,”ignore”和”replace”。改動後的程式如下:

    1234 >>>u'Hello 廣州'.encode('ascii','replace')'Hello ??'>>>u'Hello 廣州'.encode('ascii','ignore')'Hello '

    隱藏的解碼

    str和unicode型別都可以用來表示字串,為了方便它們之間進行操作,python並不要求在操作之前統一型別,所以下面的程式碼是合法的,並且能得到正確的輸出:

    123 >>>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 是最早的編碼方式,是許多編碼方式的子集。

    不過正是這個不可見的解碼過程,有時候會導致出乎意料的解碼錯誤,考慮下面的程式碼:

    1234 >>>u'Welcome to'+'廣州'Traceback(most recent call

    相關推薦

    Python 2.x 字元編碼終極指南

    在人機互動之字元編碼 一文中對字元編碼進行了詳細的討論,並通過一些簡單的小程式驗證了我們對於字元編碼的認識。但僅瞭解這篇文章的內容,並不能幫我們在日常程式設計中躲過一些字元編碼相關的坑,Stackoverflow 上就有大量編碼相關的問題,比如 1,2,3。 本文首先嚐試對編

    Python 2.x中常見字元編碼和解碼方面的錯誤及其解決辦法

    Python 2.x中的字元編碼,設計的的確不好,導致初學者,甚至是即使用Python很長時間的人,都會經常遇到字元編解碼方面的錯誤。 下面就把一些常見情,儘量的都整理出來,並給出相應的解決辦法。 看此文之前 Python中字元編碼所涉及的背後邏輯(從你輸入字元,到

    Python學習【1.1.2】-字元編碼

    字元編碼 A)文字與直譯器 1)文字編輯器存取檔案的原理(nodepad++/pycharm/word等) 開啟編輯器就打開了啟動了一個程序,是在記憶體中,所以在編輯器編寫的內容也是存放在記憶體中,斷電後資料丟失,所以需要儲存到硬碟上,點選儲存按鈕之後,就從記憶體中把資料刷到

    [python 2.x] xml.etree.ElementTree module

    print creat imp system bool .py mark ati mit XML 文件:xmlparse.xml <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTY

    python 2.x中的中文

    decode spa 哈哈 記錄 markdown 入學 深入 大堆 str 先不管一大堆的中文顯示的原理,在這裏記錄下正確顯示中文的方式,便於以後的查閱和深入學習。 方法1 a = {} a["哈哈哈"] = "啦啦啦啦啦啦啦" s1 = "{}".format(a)

    python 2.x編解碼

    文件 utf8 獲取 asc sys sci enc port imp #-- coding:utf-8 -- #文件指定為utf-8編碼 Python 2.x的程序默認編碼為asciiimport sysprint(sys.getdefaultencoding()

    Python 2.x 與3.x的版本區別介紹

    Python因其開源與其語言簡潔優美等優點,受到很多人的喜愛與使用,所以Python的更新換代也很快,這裡就簡單介紹一下2.x 與3.x的版本區別,這些資訊在官網上也都是可以找到的,因為就是官網更改,我們使用的,對於這個資訊我們都是傳遞者。 Python 2.x 與3.x的版本區別介紹

    python學習day9 字元編碼和檔案處理

    1.字元編碼 x='上' #unicode的二進位制---------》編碼--------》gbk格式的二進位制 res=x.encode('gbk') #bytes 位元組型別 print(res,type(res)) m=res.decode('gbk')  print(m)&nb

    python語法_字元編碼

    二進位制: ascll:只能存英文和拉聽字元,一個字元佔一個位元組,8位  gb2312:只能存6700多箇中文,1980年 gbk1.0:能存2萬多字元,1995年 gbk18030:2000 27000萬字符 unicode:統一各個國家的編碼,萬國碼。每個位元組佔四個位元組, 最初

    Python學習】字元編碼

    先說兩個基礎知識。 (1)計算機內部,資料是由0,1組成的; (2)計算機最小的資料單位,就是一個二進位制單位即bit,接下來就是8個二進位制單位表示一個位元組(Byte)。 1 ASCII碼 ASCII碼(American Standard Code for Information Intercha

    Python 2.xPython 3.x 的區別

    前言 如果你是剛接觸 Python 的初學者,那你可能是直接學習 Python 3.x 版本。對於 Python 2.x 的版本是不會有所接觸。官方也宣佈在 2020 停止對 Python 2.x 的維護。我也建議初學者直接去學 Python 3.x 版本。但我們還是要去了解下 Pyth

    Python 2.xPython 3​​.x的區別

    1、print 函式 print語句沒有了,取而代之的是print()函式。 Python 2.6與Python 2.7部分地支援這種形式的print語法。在Python 2.6與Python 2.7裡面,以下三種形式是等價的: print "cat" print ("c

    Day 9——java多執行緒2字元編碼集合

    java.lang.Runnable 介面 Runnable中有Public void run();方法 供現有Runnable物件建立執行緒 使用Runnable物件建立執行緒 New Thread(Runnable r).start(); 靜態同步方法

    Python基礎之字元編碼

    前言 字元編碼特別容易出問題,我們應該牢記下面的幾句話: 用什麼編碼儲存的,就要用什麼編碼開啟。 程式的執行,是先將檔案讀入記憶體中。 unicode是父編碼,只能encode解碼或其他編碼格式 utf-8,GBK這些是子8編碼,只能dec

    Python 2.x升級到3.x

    伺服器上預設安裝的是2.7版本的python,現在想手動升到3.x版本 記錄一下 1.去官網/映象下載3.x的安裝包下載網址 2.解壓檔案,執行./configure進行配置 該步驟報錯一般都是沒裝編譯器 –》 yum install make gcc g

    python 入門之 – 字元編碼及相應處理方法(二十一)

    字元編碼及操作方法 今天來了解了解關於字元編碼的一些知識,關於字元編碼,其實我們生活當中所有的智慧裝置都有字元編碼,並且每個國家都有自己的字元編碼,記錄著各個國家的文字資訊,例如早期的一些網站專案都是用中國的 gbk 編碼。 每個國家的計算機編碼只能識別自己國家的文字,如果非當前國家

    python 獲取檔案字元編碼型別

    被Windows記事本的utf-8編碼坑了一上午,python 按utf-8編碼讀取出來的內容總是有問題。最後通過程式獲取檔案編碼型別才發現,記事本的utf-8是帶BOM的!要用utf-8-sig型別讀取。 獲取編碼型別的示例程式: import chardet #

    Python模組、字元編碼、檔案讀寫

    模組 當我們用一種語言開始真正的軟體開發室,除了編寫程式碼外,還需要很多基本的已經寫好的現成的東西,高階語言通常都會為我們提供了一個比較完善的基礎程式碼庫,讓我們能直接呼叫,python也不例外,為我們提供了分成完善的基礎程式碼和第三方庫。在python中一個.py檔案就是一個模組。 模組匯

    Python入門基礎--字元編碼與檔案處理

    字元編碼 文字編輯器存取檔案的原理 #1、開啟編輯器就打開了啟動了一個程序,是在記憶體中的,所以,用編輯器編寫的內容也都是存放與記憶體中的,斷電後資料丟失 #2、要想永久儲存,需要點選儲存按鈕:編輯器把記憶體的資料存到硬碟上。 #3、在我們編寫一個py檔案(沒有執行),跟編寫其他檔案沒有任何區別

    聊一聊 Python 2 中的編碼

    在 Python 尤其是 Python2 中,編碼問題是困擾開發者尤其初學者的一大問題。什麼 Unicode/UTF-8/str ,又是 decode/encode 的,搞得人頭都大了。其實不然,這有點類似 Java 中 java.io 包一樣,看似龐大難懂,但是可以非常精細地