詳解Python2.x中對(duì)Unicode編碼的使用
我確定有很多關(guān)于Unicode和Python的說(shuō)明,但為了方便自己的理解使用,我還是打算再寫(xiě)一些關(guān)于它們的東西。
字節(jié)流 vs Unicode對(duì)象
我們先來(lái)用Python定義一個(gè)字符串。當(dāng)你使用string類型時(shí),實(shí)際上會(huì)儲(chǔ)存一個(gè)字節(jié)串。
[ a ][ b ][ c ] = "abc" [ 97 ][ 98 ][ 99 ] = "abc"
在這個(gè)例子里,abc這個(gè)字符串是一個(gè)字節(jié)串。97.,98,,99是ASCII碼。Python 2.x版本的一個(gè)不足之處就是默認(rèn)將所有的字符串當(dāng)做ASCII來(lái)對(duì)待。不幸的是,ASCII在拉丁式字符集里是最不常見(jiàn)的標(biāo)準(zhǔn)。
ASCII是用前127個(gè)數(shù)字來(lái)做字符映射。像windows-1252和UTF-8這樣的字符映射有相同的前127個(gè)字符。在你的字符串里每個(gè)字節(jié)的值低于127的時(shí)候是安全的混合字符串編碼。然而作這個(gè)假設(shè)是件很危險(xiǎn)的事情,下面還將會(huì)提到。
當(dāng)你的字符串里有字節(jié)的值大于126的時(shí)候就會(huì)出現(xiàn)問(wèn)題了。我們來(lái)看一個(gè)用windows-1252編碼的字符串。Windows-1252里的字符映射是8位的字符映射,那么總共就會(huì)有256個(gè)字符。前127個(gè)跟ASCII是一樣的,接下來(lái)的127個(gè)是由windows-1252定義的其他字符。
A windows-1252 encoded string looks like this: [ 97 ] [ 98 ] [ 99 ] [ 150 ] = "abc–"
Windows-1252仍然是一個(gè)字節(jié)串,但你有沒(méi)有看到最后一個(gè)字節(jié)的值是大于126的。如果Python試著用默認(rèn)的ASCII標(biāo)準(zhǔn)來(lái)解碼這個(gè)字節(jié)流,它就會(huì)報(bào)錯(cuò)。我們來(lái)看當(dāng)Python解碼這個(gè)字符串的時(shí)候會(huì)發(fā)生什么:
>>> x = "abc" + chr(150) >>> print repr(x) 'abc\x96' >>> u"Hello" + x Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeDecodeError: 'ASCII' codec can't decode byte 0x96 in position 3: ordinal not in range(128)
我們來(lái)用UTF-8來(lái)編碼另一個(gè)字符串:
A UTF-8 encoded string looks like this: [ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–" [0x61] [0x62] [0x63] [0xe2] [ 0x80] [ 0x93] = "abc-"
如果你拿起看你熟悉的Unicode編碼表,你會(huì)發(fā)現(xiàn)英文的破折號(hào)對(duì)應(yīng)的Unicode編碼點(diǎn)為8211(0×2013)。這個(gè)值大于ASCII最大值127。大于一個(gè)字節(jié)能夠存儲(chǔ)的值。因?yàn)?211(0×2013)是兩個(gè)字節(jié),UTF-8必須利用一些技巧告訴系統(tǒng)存儲(chǔ)一個(gè)字符需要三個(gè)字節(jié)。我們?cè)賮?lái)看當(dāng)Python準(zhǔn)備用默認(rèn)的ASCII來(lái)編碼一個(gè)里面有字符的值大于126的UTF-8編碼字符串。
>>> x = "abc\xe2\x80\x93" >>> print repr(x) 'abc\xe2\x80\x93' >>> u"Hello" + x Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeDecodeError: 'ASCII' codec can't decode byte 0xe2 in position 3: ordinal not in range(128)
你可以看到,Python一直是默認(rèn)使用ASCII編碼。當(dāng)它處理第4個(gè)字符的時(shí)候,因?yàn)樗闹禐?26大于126,所以Python拋出了錯(cuò)誤。這就是混合編碼所帶來(lái)的問(wèn)題。
解碼字節(jié)流
在一開(kāi)始學(xué)習(xí)Python Unicode 的時(shí)候,解碼這個(gè)術(shù)語(yǔ)可能會(huì)讓人很疑惑。你可以把字節(jié)流解碼成一個(gè)Unicode對(duì)象,把一個(gè)Unicode 對(duì)象編碼為字節(jié)流。
Python需要知道如何將字節(jié)流解碼為Unicode對(duì)象。當(dāng)你拿到一個(gè)字節(jié)流,你調(diào)用它的“解碼方法來(lái)從它創(chuàng)建出一個(gè)Unicode對(duì)象。
你最好是盡早的將字節(jié)流解碼為Unicode。
>>> x = "abc\xe2\x80\x93" >>> x = x.decode("utf-8") >>> print type(x) <type 'unicode'> >>> y = "abc" + chr(150) >>> y = y.decode("windows-1252") >>> print type(y) >>> print x + y abc–abc–
將Unicode編碼為字節(jié)流
Unicode對(duì)象是一個(gè)文本的編碼不可知論的代表。你不能簡(jiǎn)單地輸出一個(gè)Unicode對(duì)象。它必須在輸出前被變成一個(gè)字節(jié)串。Python會(huì)很適合做這樣的工作,盡管Python將Unicode編碼為字節(jié)流時(shí)默認(rèn)是適用ASCII,這個(gè)默認(rèn)的行為會(huì)成為很多讓人頭疼的問(wèn)題的原因。
>>> u = u"abc\u2013" >>> print u Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode character u'\u2013' in position 3: ordinal not in range(128) >>> print u.encode("utf-8") abc–
使用codecs模塊
codecs模塊能在處理字節(jié)流的時(shí)候提供很大幫助。你可以用定義的編碼來(lái)打開(kāi)文件并且你從文件里讀取的內(nèi)容會(huì)被自動(dòng)轉(zhuǎn)化為Unicode對(duì)象。
試試這個(gè):
>>> import codecs >>> fh = codecs.open("/tmp/utf-8.txt", "w", "utf-8") >>> fh.write(u"\u2013") >>> fh.close()
它所做的就是拿到一個(gè)Unicode對(duì)象然后將它以u(píng)tf-8編碼寫(xiě)入到文件。你也可以在其他的情況下這么使用它。
試試這個(gè):
當(dāng)從一個(gè)文件讀取數(shù)據(jù)的時(shí)候,codecs.open 會(huì)創(chuàng)建一個(gè)文件對(duì)象能夠自動(dòng)將utf-8編碼文件轉(zhuǎn)化為一個(gè)Unicode對(duì)象。
我們接著上面的例子,這次使用urllib流。
>>> stream = urllib.urlopen("http://www.google.com") >>> Reader = codecs.getreader("utf-8") >>> fh = Reader(stream) >>> type(fh.read(1)) <type 'unicode'> >>> Reader <class encodings.utf_8.StreamReader at 0xa6f890>
單行版本:
>>> fh = codecs.getreader("utf-8")(urllib.urlopen("http://www.google.com")) >>> type(fh.read(1))
你必須對(duì)codecs模塊十分小心。你傳進(jìn)去的東西必須是一個(gè)Unicode對(duì)象,否則它會(huì)自動(dòng)將字節(jié)流作為ASCII進(jìn)行解碼。
>>> x = "abc\xe2\x80\x93" # our "abc-" utf-8 string >>> fh = codecs.open("/tmp/foo.txt", "w", "utf-8") >>> fh.write(x) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.5/codecs.py", line 638, in write return self.writer.write(data) File "/usr/lib/python2.5/codecs.py", line 303, in write data, consumed = self.encode(object, self.errors) UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 3: ordinal not in range(128)
哎呦我去,Python又開(kāi)始用ASCII來(lái)解碼一切了。
將UTF-8字節(jié)流切片的問(wèn)題
因?yàn)橐粋€(gè)UTF-8編碼串是一個(gè)字節(jié)列表,len( )和切片操作無(wú)法正常工作。首先用我們之前用的字符串。
[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"
接下來(lái)做以下的:
>>> my_utf8 = "abc–" >>> print len(my_utf8) 6
神馬?它看起來(lái)是4個(gè)字符,但是len的結(jié)果說(shuō)是6。因?yàn)閘en計(jì)算的是字節(jié)數(shù)而不是字符數(shù)。
>>> print repr(my_utf8) 'abc\xe2\x80\x93'
現(xiàn)在我們來(lái)切分這個(gè)字符串。
>>> my_utf8[-1] # Get the last char '\x93'
我去,切分結(jié)果是最后一字節(jié),不是最后一個(gè)字符。
為了正確的切分UTF-8,你最好是解碼字節(jié)流創(chuàng)建一個(gè)Unicode對(duì)象。然后就能安全的操作和計(jì)數(shù)了。
>>> my_unicode = my_utf8.decode("utf-8") >>> print repr(my_unicode) u'abc\u2013' >>> print len(my_unicode) 4 >>> print my_unicode[-1] –
當(dāng)Python自動(dòng)地編碼/解碼
在一些情況下,當(dāng)Python自動(dòng)地使用ASCII進(jìn)行編碼/解碼的時(shí)候會(huì)拋出錯(cuò)誤。
第一個(gè)案例是當(dāng)它試著將Unicode和字節(jié)串合并在一起的時(shí)候。
>>> u"" + u"\u2019".encode("utf-8") Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)
在合并列表的時(shí)候會(huì)發(fā)生同樣的情況。Python在列表里有string和Unicode對(duì)象的時(shí)候會(huì)自動(dòng)地將字節(jié)串解碼為Unicode。
>>> ",".join([u"This string\u2019s unicode", u"This string\u2019s utf-8".encode("utf-8")]) Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 11: ordinal not in range(128)
或者當(dāng)試著格式化一個(gè)字節(jié)串的時(shí)候:
>>> "%s\n%s" % (u"This string\u2019s unicode", u"This string\u2019s utf-8".encode("utf-8"),) Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 11: ordinal not in range(128)
基本上當(dāng)你把Unicode和字節(jié)串混在一起用的時(shí)候,就會(huì)導(dǎo)致出錯(cuò)。
在這個(gè)例子里面,你創(chuàng)建一個(gè)utf-8文件,然后往里面添加一些Unicode對(duì)象的文本。就會(huì)報(bào)UnicodeDecodeError錯(cuò)誤。
>>> buffer = [] >>> fh = open("utf-8-sample.txt") >>> buffer.append(fh.read()) >>> fh.close() >>> buffer.append(u"This string\u2019s unicode") >>> print repr(buffer) ['This file\xe2\x80\x99s got utf-8 in it\n', u'This string\u2019s unicode'] >>> print "\n".join(buffer) Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 9: ordinal not in range(128)
你可以使用codecs模塊把文件作為Unicode加載來(lái)解決這個(gè)問(wèn)題。
>>> import codecs >>> buffer = [] >>> fh = open("utf-8-sample.txt", "r", "utf-8") >>> buffer.append(fh.read()) >>> fh.close() >>> print repr(buffer) [u'This file\u2019s got utf-8 in it\n', u'This string\u2019s unicode'] >>> buffer.append(u"This string\u2019s unicode") >>> print "\n".join(buffer) This file's got utf-8 in it This string's unicode
正如你看到的,由codecs.open 創(chuàng)建的流在當(dāng)數(shù)據(jù)被讀取的時(shí)候自動(dòng)地將比特串轉(zhuǎn)化為Unicode。
最佳實(shí)踐
1.最先解碼,最后編碼
2.默認(rèn)使用utf-8編碼
3.使用codecs和Unicode對(duì)象來(lái)簡(jiǎn)化處理
最先解碼意味著無(wú)論何時(shí)有字節(jié)流輸入,需要盡早將輸入解碼為Unicode。這會(huì)防止出現(xiàn)len( )和切分utf-8字節(jié)流發(fā)生問(wèn)題。
最后編碼意味著只有你打算將文本輸出到某個(gè)地方時(shí),才把它編碼為字節(jié)流。這個(gè)輸出可能是一個(gè)文件,一個(gè)數(shù)據(jù)庫(kù),一個(gè)socket等等。只有在處理完成之后才編碼unicode對(duì)象。最后編碼也意味著,不要讓Python為你編碼Unicode對(duì)象。Python將會(huì)使用ASCII編碼,你的程序會(huì)崩潰。
默認(rèn)使用UTF-8編碼意味著:因?yàn)閁TF-8可以處理任何Unicode字符,所以你最好用它來(lái)替代windows-1252和ASCII。
codecs模塊能夠讓我們?cè)谔幚碇T如文件或socket這樣的流的時(shí)候能少踩一些坑。如果沒(méi)有codecs提供的這個(gè)工具,你就必須將文件內(nèi)容讀取為字節(jié)流,然后將這個(gè)字節(jié)流解碼為Unicode對(duì)象。
codecs模塊能夠讓你快速的將字節(jié)流轉(zhuǎn)化為Unicode對(duì)象,省去很多麻煩。
解釋UTF-8
最后的部分是讓你能對(duì)UTF-8有一個(gè)入門(mén)的了解,如果你是個(gè)超級(jí)極客可以無(wú)視這一段。
利用UTF-8,任何在127和255之間的字節(jié)是特別的。這些字節(jié)告訴系統(tǒng)這些字節(jié)是多字節(jié)序列的一部分。
Our UTF-8 encoded string looks like this: [ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"
最后3字節(jié)是一個(gè)UTF-8多字節(jié)序列。如果你把這三個(gè)字節(jié)里的第一個(gè)轉(zhuǎn)化為2進(jìn)制可以看到以下的結(jié)果:
11100010
前3比特告訴系統(tǒng)它開(kāi)始了一個(gè)3字節(jié)序列226,128,147。
那么完整的字節(jié)序列。
11100010 10000000 10010011
然后你對(duì)三字節(jié)序列運(yùn)用下面的掩碼。(詳見(jiàn)這里)
1110xxxx 10xxxxxx 10xxxxxx XXXX0010 XX000000 XX010011 Remove the X's 0010 000000 010011 Collapse the numbers 00100000 00010011 Get Unicode number 0x2013, 8211 The "–"
這里僅僅是關(guān)于UTF-8的一些入門(mén)的基本知識(shí),如果想知道更多的細(xì)節(jié),可以去看UTF-8的維基頁(yè)面。
相關(guān)文章
python相對(duì)企業(yè)語(yǔ)言優(yōu)勢(shì)在哪
在本篇文章里小編給大家分享的是關(guān)于python相對(duì)企業(yè)語(yǔ)言優(yōu)勢(shì)以及相關(guān)知識(shí)點(diǎn),需要的朋友們可以參考下。2020-06-06用python生成(動(dòng)態(tài)彩色)二維碼的方法(使用myqr庫(kù)實(shí)現(xiàn))
今天小編就為大家分享一篇用python生成(動(dòng)態(tài)彩色)二維碼的方法(使用myqr庫(kù)實(shí)現(xiàn)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-06-06Django框架實(shí)現(xiàn)的分頁(yè)demo示例
這篇文章主要介紹了Django框架實(shí)現(xiàn)的分頁(yè)demo,結(jié)合實(shí)例形式分析了Django框架分頁(yè)的步驟、原理、相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-05-05python3中@dataclass的實(shí)現(xiàn)示例
@dataclass?是 Python 3.7 引入的一個(gè)裝飾器,用于方便地定義符合數(shù)據(jù)類協(xié)議的類,本文主要介紹了python3中@dataclass的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-02-02