摘要:值得注意的是,有的編碼方案不一定能表示某些信息,這時(shí)編碼就會(huì)失敗,比如就不能用來(lái)表示中文。數(shù)組的每一項(xiàng)是一個(gè)字節(jié),用來(lái)表示。所以對(duì)于字符串來(lái)說(shuō),其長(zhǎng)度等于編碼后字節(jié)的長(zhǎng)度。所以,讓來(lái)編碼解碼中文,就超出了其能力范圍。
在人機(jī)交互之字符編碼 一文中對(duì)字符編碼進(jìn)行了詳細(xì)的討論,并通過(guò)一些簡(jiǎn)單的小程序驗(yàn)證了我們對(duì)于字符編碼的認(rèn)識(shí)。但僅了解這篇文章的內(nèi)容,并不能幫我們?cè)谌粘>幊讨卸氵^(guò)一些字符編碼相關(guān)的坑,Stackoverflow 上就有大量編碼相關(guān)的問(wèn)題,比如 1,2,3。
本文首先嘗試對(duì)編碼、解碼進(jìn)行一個(gè)宏觀、直觀的解讀,然后詳細(xì)來(lái)解釋 python2 中的str和unicode,并對(duì)常見(jiàn)的UnicodeEncodeError 和 UnicodeDecodeError 異常進(jìn)行剖析。
如何理解編、解碼?如何去理解編碼、解碼?舉個(gè)例子,Alice同學(xué)剛加入了機(jī)器學(xué)習(xí)這門課,想給同班的Bob同學(xué)打個(gè)招呼。但是作為人,Alice不能通過(guò)意念和Bob交流,必須通過(guò)某種方式,比如手語(yǔ)、聲音、文字等來(lái)表達(dá)自己的想法。如果Alice選擇用文字,那么他可能會(huì)寫下這么一段文字:My name is: boot …… 來(lái)學(xué)機(jī)器學(xué)習(xí)嘍,寫文字這個(gè)過(guò)程其實(shí)就是編碼,經(jīng)過(guò)編碼后的文字才能給Bob看。Bob收到Alice的文字后,就會(huì)用自己對(duì)文字的認(rèn)知來(lái)解讀Alice傳達(dá)的含義,這個(gè)過(guò)程其實(shí)就是解碼。當(dāng)然,如果Bob不懂中文,那么就無(wú)法理解Alice的最后一句了,如果Bob不識(shí)字,就完全不知道Alice想表達(dá)什么了。
上面的例子只是為了方便我們理解編碼、解碼這個(gè)抽象的概念,現(xiàn)在來(lái)看看對(duì)于計(jì)算機(jī)程序來(lái)說(shuō),如何去理解字符的編碼、解碼過(guò)程。我們知道絕大多數(shù)程序都是讀取數(shù)據(jù),做一些操作,然后輸出數(shù)據(jù)。比如當(dāng)我們打開(kāi)一個(gè)文本文件時(shí),就會(huì)從硬盤讀取文件中的數(shù)據(jù),接著我們輸入了新的數(shù)據(jù),點(diǎn)擊保存后,文本程序會(huì)將更新后的內(nèi)容輸出到硬盤。程序讀取數(shù)據(jù)就相當(dāng)于Bob讀文字,必須進(jìn)行一個(gè)解碼的過(guò)程,解碼后的數(shù)據(jù)才能讓我們進(jìn)行各種操作。同理,保存到硬盤時(shí),也需要對(duì)數(shù)據(jù)進(jìn)行編碼。
下圖方框 A 代表一個(gè)輸出數(shù)據(jù)的程序,方框 B 代表一個(gè)讀取數(shù)據(jù)的程序。當(dāng)然這里的程序只是一個(gè)概念,表示一個(gè)處理數(shù)據(jù)的邏輯單元,可以是一個(gè)進(jìn)程、一個(gè)函數(shù)甚至一個(gè)語(yǔ)句等。A 和 B 也可以是同一個(gè)程序,先解碼外部獲取的數(shù)據(jù),內(nèi)部操作后,再進(jìn)行某種編碼。
值得注意的是,有的編碼方案不一定能表示某些信息,這時(shí)編碼就會(huì)失敗,比如 ASCII 就不能用來(lái)表示中文。當(dāng)然,如果以錯(cuò)誤的方式去解讀某段內(nèi)容,解碼也會(huì)失敗,比如用 ASCII 來(lái)解讀包含 UTF-8的信息。至于什么是 ASCII,UTF-8等,在人機(jī)交互之字符編碼 中有詳細(xì)的說(shuō)明,這里不再贅述。下面結(jié)合具體的例子,來(lái)看看編碼、解碼的細(xì)節(jié)問(wèn)題。
python2.x 中的字符串在程序設(shè)計(jì)中,字符串一般是指一連串的字符,比如hello world!、你好或者もしもし(日語(yǔ))等等。各種語(yǔ)言對(duì)于字符串的支持各不相同,Python 2 中字符串的設(shè)計(jì)頗不合理,導(dǎo)致新手經(jīng)常會(huì)出現(xiàn)各種問(wèn)題,類似下面的提示信息相信很多人都遇到過(guò)(UnicodeEncodeError 或者 UnicodeDecodeError):
Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: "ascii" codec can"t encode characters in position 0-1: ordinal not in range(128)
下面我們一起來(lái)解決這個(gè)疑難雜癥。首先需要搞清楚python中的兩個(gè)類型:
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".
上面并沒(méi)有給出什么有用的信息,不過(guò)好在這篇文章講的特別好,簡(jiǎn)單來(lái)說(shuō):
str:是字節(jié)串(container for bytes),由 Unicode 經(jīng)過(guò)編碼(encode)后的字節(jié)組成的。
unicode:真正意義上的字符串,其中的每個(gè)字符用 Unicode 中對(duì)應(yīng)的 Code Point 表示。
翻譯成人話就是,unicode 有點(diǎn)類似于前面 Alice 打招呼傳遞的想法,而 str 則是寫下來(lái)的文字(或者是說(shuō)出來(lái)的聲音,甚至可以是手語(yǔ))。我們可以用 GBK,UTF-8 等編碼方案將 Unicode 類型轉(zhuǎn)換為 str 類型,類似于用語(yǔ)言、文字或者手語(yǔ)來(lái)表達(dá)想法。
repr 與終端交互為了徹底理解字符編碼、解碼,下面要用 python 交互界面進(jìn)行一些小實(shí)驗(yàn)來(lái)加深我們的理解(下面所有的交互代碼均在 Linux 平臺(tái)下)。在這之前,我們先來(lái)看下面交互代碼:
>>> demo = "Test 試試" >>> demo "Test xe8xafx95xe8xafx95"
當(dāng)我們只輸入標(biāo)識(shí)符 demo 時(shí),終端返回了 demo 的內(nèi)容。這里返回的內(nèi)容是怎么得到呢?答案是通過(guò) repr() 函數(shù) 獲得。文檔中對(duì)于 repr 函數(shù)解釋如下:
Return a string containing a printable representation of an object.
所以,我們可以在源文件中用下面的代碼,來(lái)獲取和上面終端一樣的輸出。
#! /usr/bin/env python # -*- coding: UTF-8 -*- demo = "Test 試試" print repr(demo) # "Test xe8xafx95xe8xafx95"
對(duì)于字符串來(lái)說(shuō),repr() 的返回值很好地說(shuō)明了其在python內(nèi)部的表示方式。通過(guò) repr 的返回值,我們可以真切體會(huì)到前面提到的兩點(diǎn):
str:實(shí)際上是字節(jié)串
unicode:真正意義上的字符串
下面分別來(lái)看看這兩個(gè)類型。
unicode 類型unicode 是真正意義上的字符串,為了理解這句話,先看下面的一段代碼:
>>> unicode_str = u"Welcome to 廣州" # ""前面的 u 表示這是一個(gè) unicode 字符串 >>> unicode_str, type(unicode_str) # repr(unicode_str) (u"Welcome to u5e7fu5dde",)
repr 返回的 Welcome to u5e7fu5dde 說(shuō)明了unicode_str存儲(chǔ)的內(nèi)容,其中兩個(gè)u后面的數(shù)字分別對(duì)應(yīng)了廣、州在unicode中的code point:
5e7f 對(duì)應(yīng)廣字;
5dde 對(duì)應(yīng)州字;
英文字母也有對(duì)應(yīng)的code point,它的值等于ASCII值,不過(guò)repr并沒(méi)有直接輸出。我們可以在站長(zhǎng)工具中查看所有字符對(duì)應(yīng)的code point。也可以用 python 的內(nèi)置函數(shù) ord 查看字符的 code point,如下所示(調(diào)用了 format 將code point轉(zhuǎn)換為十六進(jìn)制):
>>> "{:04x}".format(ord(u"廣")) "5e7f" >>> "{:04x}".format(ord(u"W")) "0057"
總結(jié)一下,我們可以將
>>> len(unicode_str) 13 >>> unicode_str[0], unicode_str[12], unicode_str[-1] (u"W", u"u5dde", u"u5dde")str 類型
str 是字節(jié)串(container for bytes),為了理解這句話,先來(lái)看下面的一段代碼:
>>> str_str = "Welcome to 廣州" # 這是一個(gè) str >>> str_str, type(str_str) ("Welcome to xe5xb9xbfxe5xb7x9e",)
python中 xhh(h為16進(jìn)制數(shù)字)表示一個(gè)字節(jié),輸出中的xe5xb9xbfxe5xb7x9e 就是所謂的字節(jié)串,它對(duì)應(yīng)了廣州。實(shí)際上 str_str 中的英文字母也是保存為字節(jié)串的,不過(guò) repr 并沒(méi)有以 x 的形式返回。為了驗(yàn)證上面輸出內(nèi)容確實(shí)是字節(jié)串,我們用python提供的 bytearray 函數(shù)將相同內(nèi)容的 unicode字符串用 UTF-8 編碼為字節(jié)數(shù)組,如下所示:
>>> unicode_str = u"Welcome to 廣州" >>> bytearray(unicode_str, "UTF-8") bytearray(b"Welcome to xe5xb9xbfxe5xb7x9e") >>> list(bytearray(unicode_str, "UTF-8")) # 字節(jié)數(shù)組,每一項(xiàng)為一個(gè)字節(jié); [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"))]) # 轉(zhuǎn)換為 xhh 的形式 x57x65x6cx63x6fx6dx65x20x74x6fx20xe5xb9xbfxe5xb7x9e
可見(jiàn),上面的 str_str 是 unicode_str 經(jīng)過(guò) UTF-8 編碼 后的字節(jié)串。這里透漏了一個(gè)十分重要的信息,
# Win 平臺(tái)下,系統(tǒng)語(yǔ)言為簡(jiǎn)體中文 >>> str_str = "Welcome to 廣州" >>> str_str, type(str_str) ("Welcome to xb9xe3xd6xdd",)
這里str_str的隱式編碼是cp936,可以用 bytearray(unicode_str, "cp936") 來(lái)驗(yàn)證這點(diǎn)。終端下,str類型的隱式編碼由系統(tǒng) locale 決定,可以采用下面方式查看:
# Unix or Linux >>> import locale >>> locale.getdefaultlocale() ("zh_CN", "UTF-8") ... # 簡(jiǎn)體中文 Windows >>> locale.getdefaultlocale() ("zh_CN", "cp936")
總結(jié)一下,我們可以將
>>> len(str_str) 17 >>> str_str[0], str_str[-1] ("W", "x9e") # 實(shí)際上是("x57", "x9e")類型轉(zhuǎn)換
Python 2.x 中為上面兩種類型的字符串都提供了 encode 和 decode 方法,原型如下:
str.decode([encoding[, errors]])
str.encode([encoding[, errors]])
利用上面的兩個(gè)函數(shù),可以實(shí)現(xiàn) str 和 unicode 類型之間的相互轉(zhuǎn)換,如下圖所示:
上圖中綠色線段標(biāo)示的即為我們常用的轉(zhuǎn)換方法,紅色標(biāo)示的轉(zhuǎn)換在 python 2.x 中是合法的,不過(guò)沒(méi)有什么意義,通常會(huì)拋出錯(cuò)誤(可以參見(jiàn) What is the difference between encode/decode?)。下面是兩種類型之間的轉(zhuǎn)換示例:
# decode:到 的轉(zhuǎn)換 >>> enc = str_str.decode("utf-8") >>> enc, type(enc) (u"Welcome to u5e7fu5dde", ) # encode: 到 的轉(zhuǎn)換 >>> dec = unicode_str.encode("utf-8") >>> dec, type(dec) ("Welcome to xe5xb9xbfxe5xb7x9e", )
上面代碼中通過(guò)encode將unicode類型編碼為str類型,通過(guò) decode 將str類型解碼為unicode類型。當(dāng)然,編碼、解碼的過(guò)程并不總是一帆風(fēng)順的,通常會(huì)出現(xiàn)各種錯(cuò)誤。
編、解碼錯(cuò)誤Python 中經(jīng)常會(huì)遇到 UnicodeEncodeError 和 UnicodeDecodeError,怎么產(chǎn)生的呢? 如下代碼所示:
>>> u"Hello 廣州".encode("ascii") Traceback (most recent call last): File "", line 1, in 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 " ", line 1, in UnicodeDecodeError: "ascii" codec can"t decode byte 0xe5 in position 6: ordinal not in range(128)
當(dāng)我們用 ascii 去編碼帶有中文的unicode字符串時(shí),發(fā)生了UnicodeEncodeError,當(dāng)我們用 ascii 去解碼有中文的str字節(jié)串時(shí),發(fā)生了UnicodeDecodeError。我們知道,ascii 只包含 127 個(gè)字符,根本無(wú)法表示中文。所以,讓 ascii 來(lái)編碼、解碼中文,就超出了其能力范圍。這就像你對(duì)一個(gè)不懂中文的老外說(shuō)中文,他根本沒(méi)法聽(tīng)懂。簡(jiǎn)單來(lái)說(shuō),所有的編碼、解碼錯(cuò)誤都是由于所選的編碼、解碼方式無(wú)法表示某些字符造成的。
有時(shí)候我們就是想用 ascii 去編碼一段夾雜中文的str字節(jié)串,并不希望拋出異常。那么可以通過(guò) errors 參數(shù)來(lái)指定當(dāng)無(wú)法編碼某個(gè)字符時(shí)的處理方式,常用的處理方式有 "strict","ignore"和"replace"。改動(dòng)后的程序如下:
>>> u"Hello 廣州".encode("ascii", "replace") "Hello ??" >>> u"Hello 廣州".encode("ascii", "ignore") "Hello "隱藏的解碼
str和unicode類型都可以用來(lái)表示字符串,為了方便它們之間進(jìn)行操作,python并不要求在操作之前統(tǒng)一類型,所以下面的代碼是合法的,并且能得到正確的輸出:
>>> new_str = u"Welcome to " + "GuangZhou" >>> new_str, type(new_str) (u"Welcome to GuangZhou",)
因?yàn)閟tr類型是隱含有某種編碼方式的字節(jié)碼,所以python內(nèi)部將其解碼為unicode后,再和unicode類型進(jìn)行 + 操作,最后返回的結(jié)果也是unicode類型。
第2步的解碼過(guò)程是在幕后悄悄發(fā)生的,默認(rèn)采用ascii來(lái)進(jìn)行解碼,可以通過(guò) sys.getdefaultencoding() 來(lái)獲取默認(rèn)編碼方式。Python 之所以采用 ascii,是因?yàn)?ascii 是最早的編碼方式,是許多編碼方式的子集。
不過(guò)正是這個(gè)不可見(jiàn)的解碼過(guò)程,有時(shí)候會(huì)導(dǎo)致出乎意料的解碼錯(cuò)誤,考慮下面的代碼:
>>> u"Welcome to" + "廣州" Traceback (most recent call last): File "", line 1, in UnicodeDecodeError: "ascii" codec can"t decode byte 0xe5 in position 0: ordinal not in range(128)
上面在字符串的+操作時(shí),python 偷偷對(duì)"廣州"用 ascii 做解碼操作,所以拋出了UnicodeDecodeError異常。其實(shí)上面操作等同于 u"Welcome to" + "廣州".decode("ascii") ,你會(huì)發(fā)現(xiàn)這句代碼拋出的異常和上面的一模一樣。
隱藏的編碼Python 不只偷偷地用 ascii 來(lái)解碼str類型的字節(jié)串,有時(shí)還會(huì)偷偷用ascii來(lái)編碼unicode類型。如果函數(shù)或類等對(duì)象接收的是 str 類型的字符串,但傳進(jìn)去的是unicode,python2 就會(huì)使用 ascii 將其編碼成str類型再做運(yùn)算。
以raw_input為例,我們可以給 raw_input 函數(shù)提供 prompt 參數(shù),作為輸入提示內(nèi)容。這里如果 prompt 是 unicode 類型,python會(huì)先用ascii對(duì)其進(jìn)行編碼,所以下面代碼會(huì)拋出UnicodeEncodeError異常:
>>> a = raw_input(u"請(qǐng)輸入內(nèi)容: ") Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: "ascii" codec can"t encode characters in position 0-4: ordinal not in range(128)
上面操作完全等同于 a = raw_input(u"請(qǐng)輸入內(nèi)容: ".encode("ascii")),你會(huì)發(fā)現(xiàn)它們拋出的異常完全一樣。此外,如果嘗試將unicode字符串重定向輸出到文本中,也可能會(huì)拋出UnicodeEncodeError異常。
$ cat a.py demo = u"Test 試試" print demo $ python a.py > output Traceback (most recent call last): File "a.py", line 5, inprint demo UnicodeEncodeError: "ascii" codec can"t encode characters in position 5-6: ordinal not in range(128)
當(dāng)然,如果直接在終端進(jìn)行輸出,則不會(huì)拋出異常。因?yàn)閜ython會(huì)使用控制臺(tái)的默認(rèn)編碼,而不是 ascii。
總結(jié)總結(jié)下本文的內(nèi)容:
str可以看作是unicode字符串經(jīng)過(guò)某種編碼后的字節(jié)組成的數(shù)組
unicode是真正意義上的字符串
通過(guò) encode 可以將unicode類型編碼為str類型
通過(guò) decode 可以將str類型解碼為unicode類型
python 會(huì)隱式地進(jìn)行編碼、解碼,默認(rèn)采用 ascii
所有的編碼、解碼錯(cuò)誤都是由于所選的編碼、解碼方式無(wú)法表示某些字符造成的
如果你明白了上面每句話的含義,那么應(yīng)該能解決大部分編、解碼引起的問(wèn)題了。當(dāng)然,本篇文章其實(shí)并不能幫你完全避免python編碼中的坑(坑太多)。還有許多問(wèn)題在這里并沒(méi)有說(shuō)明:
讀取、寫入文件時(shí)的編碼問(wèn)題:
數(shù)據(jù)庫(kù)的讀寫
網(wǎng)絡(luò)數(shù)據(jù)操作
源文件編碼格式的指定
有空再詳細(xì)談?wù)勆厦媪谐龅目印?/p>
更多閱讀本文由selfboot 發(fā)表于個(gè)人博客,采用署名-非商業(yè)性使用-相同方式共享 3.0 中國(guó)大陸許可協(xié)議。
非商業(yè)轉(zhuǎn)載請(qǐng)注明作者及出處。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者本人
本文標(biāo)題為: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-進(jìn)階-編碼處理小結(jié)
五分鐘戰(zhàn)勝 Python 字符編碼
python 字符編碼與解碼
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/38295.html
摘要:使用中文替代中文中文編碼中文編碼中有以上兩種聲明字符串變量的方式,它們的主要區(qū)別是編碼格式的不同,其中,的編碼格式和文件聲明的編碼格式一致,而的編碼格式則是。 字符串是Python中最常用的數(shù)據(jù)類型,而且很多時(shí)候你會(huì)用到一些不屬于標(biāo)準(zhǔn)ASCII字符集的字符,這時(shí)候代碼就很可能拋出UnicodeDecodeError: ascii codec cant decode byte 0xc4 ...
摘要:,,等屬于不同的字符集,轉(zhuǎn)換編碼就是在它們中的任意兩者間進(jìn)行。一般個(gè)人用的電腦上控制臺(tái)基本上都是編碼的,但運(yùn)維的機(jī)器上基本全是,中文的時(shí)候就會(huì)有酸爽的問(wèn)題。 總結(jié)總結(jié),本文僅適用于python2.x 默認(rèn)編碼與開(kāi)頭聲明 首先是開(kāi)頭的地方聲明編碼 # coding: utf8 這個(gè)東西的用處是聲明文件編碼為utf8(要寫在前兩行內(nèi)),不然文件里如果有中文,比如 a = 美麗 b = u美...
摘要:一積累中如何快速查看包中的源碼最常用的大開(kāi)發(fā)快捷鍵技巧將對(duì)象保存到文件中從文件中讀取對(duì)象中的用法的配置詳解和代碼的格式詳解格式化內(nèi)容設(shè)置生成詳解注釋規(guī)范中設(shè)置內(nèi)存調(diào)試的小知識(shí)單步執(zhí)行命令的區(qū)別的動(dòng)態(tài)代理機(jī)制詳解內(nèi)容有瑕疵,樓指正泛型繼承的幾 一、積累 1.JAVA Eclipse中如何快速查看jar包中 的class源碼 最常用的15大Eclipse開(kāi)發(fā)快捷鍵技巧 Java將對(duì)象保存到...
摘要:一積累中如何快速查看包中的源碼最常用的大開(kāi)發(fā)快捷鍵技巧將對(duì)象保存到文件中從文件中讀取對(duì)象中的用法的配置詳解和代碼的格式詳解格式化內(nèi)容設(shè)置生成詳解注釋規(guī)范中設(shè)置內(nèi)存調(diào)試的小知識(shí)單步執(zhí)行命令的區(qū)別的動(dòng)態(tài)代理機(jī)制詳解內(nèi)容有瑕疵,樓指正泛型繼承的幾 一、積累 1.JAVA Eclipse中如何快速查看jar包中 的class源碼 最常用的15大Eclipse開(kāi)發(fā)快捷鍵技巧 Java將對(duì)象保存到...
閱讀 2539·2023-04-25 21:41
閱讀 1686·2021-09-22 15:17
閱讀 1961·2021-09-22 10:02
閱讀 2470·2021-09-10 11:21
閱讀 2610·2019-08-30 15:53
閱讀 1030·2019-08-30 15:44
閱讀 972·2019-08-30 13:46
閱讀 1205·2019-08-29 18:36