說(shuō)明:本文是我在readthedocs看到的,覺(jué)得很不錯(cuò)所以轉(zhuǎn)載過(guò)來(lái),有刪改,原文地址點(diǎn)這里。
實(shí)用Unicode編程指南
這是我在Pycon2012所做的演講。你可以閱讀本頁(yè)的幻燈片和文字,或者直接在瀏覽器中打開演示,或者來(lái)看現(xiàn)場(chǎng)視頻。
同時(shí),點(diǎn)擊文章的圖片將會(huì)進(jìn)入所在幻燈片的的對(duì)應(yīng)位置,圖片中使用了 Symbola 字體,但是如果想要顯示一些特殊符號(hào)的話,則需先將該字體下載下來(lái)。
大家好,我是Ned Batchelder.我已經(jīng)有十年的Python編程經(jīng)驗(yàn),這意味著,很多很多的時(shí)候,我與其他程序員一樣,犯過(guò)很多Unicode的編碼錯(cuò)誤。
如果你和其他Python 程序員一樣,那你肯定也碰到過(guò)如下情況:你編寫了一段很漂亮的代碼,事情看起來(lái)很順。然后某一天一個(gè)很奇怪的方言字符不知道從哪冒了出來(lái),你的程序中就開始大量涌現(xiàn)UnicodeErrors。
你好像知道這種問(wèn)題應(yīng)該怎樣解決,于是呢,就去在錯(cuò)誤出現(xiàn)的地方添加了encode和decode,但是UnicodeError又開始出現(xiàn)在其他的地方。于是你又在另外一個(gè)地方添加了decode抑或encode。在你玩過(guò)一段編碼打地鼠游戲之后,問(wèn)題似乎被解決。
之后某一天,另一種方言字符又在另外一個(gè)地方出現(xiàn)了,然后你不得不又去來(lái)玩這種打地鼠直到問(wèn)題解決掉。
現(xiàn)在你的程序終于可以運(yùn)行。但是你又煩惱又不適,這個(gè)問(wèn)題花費(fèi)了太多時(shí)間,你知道這樣解決正確,于是開始憎恨自己。你對(duì)Unicode的主要了解就是你很討厭它。
你不想去了解怪異的字符集,你只想要寫一個(gè)你認(rèn)為不是很糟糕的程序。
你不必去玩打地鼠游戲,Unicode會(huì)有些麻煩,但是它并不難。了解了相關(guān)知識(shí)并且加以練習(xí),你也可以方便的優(yōu)雅的解決相關(guān)問(wèn)題。
接下來(lái)我會(huì)教給你five facts of life,然后給你一些專業(yè)建議來(lái)解決Unicode問(wèn)題。下面的內(nèi)容將會(huì)包含Unicode基本知識(shí),如何在Python 2和Python 3中來(lái)實(shí)現(xiàn)。他們有一定差異,但是你使用的基本策略都是一樣的。
世界&Unicode
我們從Unicode基本知識(shí)開始。
事實(shí)之一:計(jì)算機(jī)中的一切均為bytes(字節(jié))。硬盤中的文件為一系列的byte組成,網(wǎng)絡(luò)中傳輸?shù)闹挥衎yte。所有的信息,在你寫的程序中進(jìn)進(jìn)出出的,均由byte組成。
孤立的byte是毫無(wú)意義的,所以我們來(lái)賦予他們含義。
為了表示各種文字,我們有大約50年的時(shí)間都在用ASCII碼。每一個(gè)byte被賦予95種符號(hào)的一種,所以,當(dāng)我給你發(fā)送byte值為65的時(shí)候,你知道我想表達(dá)一個(gè)a。
ISO Latin 1,或者8859-1對(duì)ASCII的96多種字符進(jìn)行了擴(kuò)展。這也許是你用一個(gè)byte可以做的最多的事情了。因?yàn)閎yte中沒(méi)有容量可以存儲(chǔ)更多的符號(hào)了。
在windows中,增加了另外27種字符。這種叫做CP1252編碼。
事實(shí)之二是,世界上的字符遠(yuǎn)遠(yuǎn)比256個(gè)要多。一個(gè)簡(jiǎn)單的byte不能夠表達(dá)世界范圍內(nèi)的字符。在你玩編碼打地鼠的時(shí)候,你多么的希望世界上所有的人都說(shuō)英語(yǔ),但是事實(shí)并不是這樣,人們需要更多的符號(hào)來(lái)交流。
事實(shí)一和事實(shí)二共同造成了計(jì)算機(jī)設(shè)備結(jié)構(gòu)與世界人類需求的一個(gè)沖突。
當(dāng)時(shí)為了解決沖突嘗試了多種途徑。通過(guò)一個(gè)byte來(lái)與符號(hào)或者字符進(jìn)行對(duì)應(yīng)的編碼,每一種解決途徑都沒(méi)有解決事實(shí)二中的實(shí)質(zhì)問(wèn)題。
當(dāng)時(shí)有很多一個(gè)byte的編碼,都沒(méi)有能夠解決問(wèn)題。每一個(gè)都只能解決人類語(yǔ)言的一部分。但是他們不能解決所有的文字問(wèn)題。
人們開始創(chuàng)造兩個(gè)byte的字符集,但是仍然像碎片一樣,只能夠服務(wù)于不同地域的一部分人。
當(dāng)時(shí)產(chǎn)生了不同的標(biāo)準(zhǔn),諷刺的是,他們都不足以滿足所有的符號(hào)的需求。
Unicode就是為了解決之前的老的字符集問(wèn)題。Unicode分配整形,被成為代碼點(diǎn)(Unicode的字符被成為代碼點(diǎn)code points,用u后面加上xxxx來(lái)表現(xiàn),其中,x為16進(jìn)制的字符)來(lái)表示字符。他有 110 萬(wàn)的代碼點(diǎn),其中有11萬(wàn)被占用,所以它有很多很多的空間可供未來(lái)的增長(zhǎng)使用。
Unicode的目的是包含一切,它從ASCII開始,包含了數(shù)以千計(jì)的代碼,包含這著名的—-雪人??包含了世界上所有的書寫系統(tǒng),而且一直在被擴(kuò)充。比如,最新的更新中,就有一大堆沒(méi)用的詞匯。
這里有六個(gè)的異國(guó)Unicode字符。Unicode代碼點(diǎn)寫成 4- , 5- ,或者 6 位的十六進(jìn)制編碼,同時(shí)有一個(gè)u的前綴。每一個(gè)字符都有一個(gè)用ASCII字符規(guī)定的名稱。
所以說(shuō)Unicode提供了所有我們需要的字符的空間。但是我們?nèi)匀恍枰幚硎聦?shí)一中所碰到的問(wèn)題:計(jì)算機(jī)只能看懂 bytes。我們需要一種用bytes來(lái)表示Unicode的方法這樣才可以存儲(chǔ)和傳播他們。
Unicode標(biāo)準(zhǔn)定義了多種方法來(lái)用 bytes來(lái)表示成代碼點(diǎn),被稱為encoding 。
UTF-8是最流行的一種對(duì)Unicode進(jìn)行傳播和存儲(chǔ)的編碼方式。它用不同的 bytes來(lái)表示每一個(gè)代碼點(diǎn)。ASCII字符每個(gè)只需要用一個(gè)byte ,與ASCII的編碼是一樣的。所以說(shuō)ASCII是UTF-8的一個(gè)子集。
這里我們展現(xiàn)了幾個(gè)怪異字符的UTF8的表示方法。ASCII字母H和字母I用一個(gè)byte就可以表示。其他的根據(jù)代碼點(diǎn)的不同使用了兩個(gè)或者三個(gè) bytes 。盡管有些并不常用,但是一些代碼點(diǎn)使用到四個(gè)bytes。
Python 2
好,說(shuō)完了這么多理論知識(shí),我們來(lái)講一講Python 2。
在Python2中,有兩種字符串?dāng)?shù)據(jù)類型。一種純舊式的文字: str對(duì)象,存儲(chǔ) bytes。如果你使用一個(gè)u前綴,那么你會(huì)有一個(gè)unicode對(duì)象,存儲(chǔ)的是 code points 。在一個(gè) unicode 字符串中,你可以使用反斜杠 u(u) 來(lái)插入任何的 unicode代碼點(diǎn)。
你可以注意到string這個(gè)詞是有問(wèn)題的。不管是str對(duì)象還是unicode對(duì)象都是一種string,但是為了直接還是將他們明確區(qū)分開來(lái)。
如果想要在unicode和bytes間轉(zhuǎn)換的話,每個(gè)都會(huì)有個(gè)方法。Unicode字符串會(huì)有一個(gè).encode方法來(lái)產(chǎn)生bytes,bytes 串會(huì)有一個(gè).decode方法來(lái)產(chǎn)生 unicode。每個(gè)方法中都會(huì)有一個(gè)參數(shù),來(lái)表明你要操作的編碼類型。
我們可以定義一個(gè)Unicode字符串叫做my_unicode,然后看這九個(gè)字符,我們使用encode方法來(lái)創(chuàng)建my_unicode的bytes串,會(huì)有19個(gè)bytes ,像你所期待的那樣,將 bytes 串來(lái)decode將會(huì)得到UTF-8串。
不幸的是,如果指明的編碼名稱錯(cuò)誤的話,那么encode和decode會(huì)產(chǎn)生錯(cuò)誤?,F(xiàn)在我們?nèi)L試encode我們的幾個(gè)詭異的字符到ASCII,會(huì)失敗。因?yàn)?ASCII只能表示 0-127個(gè)字符中的一個(gè)。然而我們的Unicode字符串早已經(jīng)超出了范圍。
拋出的異常為UnicodeEncodeError,它展現(xiàn)了你使用的編碼方式,codec即編碼解碼器,展現(xiàn)了導(dǎo)致問(wèn)題的字符的位置。
解碼同樣會(huì)知道出一些問(wèn)題?,F(xiàn)在我們?nèi)グ岩粋€(gè)UTF-8字符串解碼成ASCII,會(huì)得到一個(gè)UnicodeDecodeError,原因一樣,ASCII只接受 127 內(nèi)的值,我們的UTF-8字符串超出了范圍。
盡管UTF-8不能解碼成任何的bytes串,我們嘗試來(lái)decode一些垃圾信息。同樣也產(chǎn)生了UnicodeDecodeError錯(cuò)誤。最終,UTF-8的優(yōu)勢(shì)是,有效的bytes 串,將會(huì)幫助我們來(lái)創(chuàng)建高魯棒性的系統(tǒng):如果數(shù)據(jù)無(wú)效的話,數(shù)據(jù)不會(huì)被接受。
當(dāng)編碼或者解碼的時(shí)候,你可以指明如果codec不能夠處理數(shù)據(jù)的時(shí)候,會(huì)發(fā)生什么情況。encode或者decode時(shí)候的第二個(gè)參數(shù)指明了規(guī)則。默認(rèn)的值是 strict,意味著像剛剛一樣,將會(huì)拋出一個(gè)異常。
replace值意味著,將會(huì)返回一個(gè)標(biāo)準(zhǔn)的替代字符。當(dāng)編碼的時(shí)候,替代值是一個(gè)問(wèn)好,所以任何不能被編碼的值將會(huì)產(chǎn)生一個(gè) ?。
一些其他的 handler非常有用。xmlcharrefreplace將會(huì)產(chǎn)生一個(gè)完全替代的html/xml字符,所以u01B4將會(huì)變成 ƴ (因?yàn)槭M(jìn)制的 01B4 是十進(jìn)制的 436 )。如果你需要將返回的值來(lái)輸出到html文件中的話,將會(huì)非常有用。
注意要根據(jù)不同的錯(cuò)誤原因使用不同的錯(cuò)誤處理方式。Replace是一個(gè)處理不能被解析的數(shù)據(jù)的自衛(wèi)型方式,會(huì)丟失數(shù)據(jù)。Xmlcharrefreplace會(huì)保護(hù)所有的原始數(shù)據(jù),在xml轉(zhuǎn)義符可以使用的時(shí)候來(lái)輸出數(shù)據(jù)。
你也可以指定在解碼的時(shí)候的錯(cuò)誤處理方式。ignore將會(huì)直接將不能解碼的 bytes 丟掉。replace將會(huì)直接添加Unicode U+FFFD,給有問(wèn)題的bytes直接替換成替換字符。注意因?yàn)榻獯a器不能解碼這些數(shù)據(jù)。它并不知道到底有多少Unicode字符。解碼我們的UTF-8字符串成為ASCII制造出了 16 個(gè)替換字符。每個(gè)byte不能被解析都被替換掉了。然而這些bytes想要表示 6 個(gè)Unicode字符。
Python 2已經(jīng)試圖在處理 unicode 和 byte 串的時(shí)候變得有用些。如果你系那個(gè)要把 Unicode 字符串串和 byte 字符串來(lái)組合起來(lái)的話,Python 2將會(huì)自動(dòng)的將 byte 串來(lái)解碼成 unicode 字符串。從而產(chǎn)生一個(gè)新的 Unicode 字符串。
比如,我們想要連接Unicode串hello和一個(gè)byte字符串world。結(jié)果是一個(gè)Unicode的hello world。在我們看來(lái)。Python 2將world使用 ASCII codec進(jìn)行了解碼。這次在解碼中使用的字符集的值與 sys.getdefaultencoding()的值相等。
這里這個(gè)系統(tǒng)中的字符集為ASCII,因?yàn)檫@是唯一的一種猜測(cè): ASCII如此被廣泛接受,它是這么多編碼的子集,不像是錯(cuò)誤的。
當(dāng)然,這些隱藏的編碼轉(zhuǎn)換不能免于去解碼錯(cuò)誤。如果你想要連接一個(gè)byte字符串和一個(gè)Unicode字符串,并且byte字符串不能被解碼成ASCII的話,將會(huì)拋出一個(gè)UnicodeDecodeError。
這就是那些可惡的UnicodeError的圓圈。你的代碼中包含了Unicode和byte字符串,只要數(shù)據(jù)全部是 ASCII 的話,所有的轉(zhuǎn)換都是正確的,一旦一個(gè)非 ASCII 字符偷偷進(jìn)入你的程序,那么默認(rèn)的解碼將會(huì)失效,從而造成UnicodeDecodeError的錯(cuò)誤。
Python 2的哲學(xué)就是 Unicode 字符串和 byte 字符串都是混亂的,他試圖去通過(guò)自動(dòng)轉(zhuǎn)換來(lái)減輕你的負(fù)擔(dān)。就像在 int 和 float 之間的轉(zhuǎn)換一樣, int 到 float 的轉(zhuǎn)換不會(huì)失敗,byte 字符串到 unicode 字符串會(huì)失敗。
Python 2悄悄掩蓋掉了 byte 到 unicode 的轉(zhuǎn)換,讓程序在處理 ASCII 的時(shí)候更加簡(jiǎn)單。你復(fù)出的代價(jià)就是在處理非 ASCII 的時(shí)候?qū)?huì)失敗。
有很多方法來(lái)合并兩個(gè)字符串,所有的都會(huì)解析 byte 成為 unicode,所以他們處理的時(shí)候你必須多加小心。
首先我們使用 ASCII 格式字符串,和 unicode 來(lái)結(jié)合。那么最終的輸出將會(huì)變成 unicode,返回一個(gè) unicode 字符串。
之后我們將兩個(gè)交換一下:一個(gè) unicode 格式的字符串和一個(gè) byte 串再一次合并,生成了一個(gè) unicode 字符串,因?yàn)?byte 串可以被解碼成 ASCII。
簡(jiǎn)單的去打印出一個(gè) unicode 字符串將會(huì)調(diào)用隱式的編碼:輸出總會(huì)是 bytes,所以在 unicode 被打印之前必須被編碼成 byte 串。
接下來(lái)的事情非常不可理解:我們讓一個(gè) byte 串編碼成 UTF-8,卻得到一個(gè)錯(cuò)誤說(shuō)不能被解碼成 ASCII!這里的問(wèn)題是 byte 串不能被編碼,要記住編碼是你將 Unicode 變成了 byte 串。所以想要執(zhí)行你的操作的話,Python2需要的是一個(gè) unicode 字符串,隱式的將你的字符串解碼成 ASCII。
最后,我們將 ASCII 字符串編碼成 UTF-8?,F(xiàn)在我們進(jìn)行相同的隱式編碼操作,因?yàn)樽址疄?ASCII,編碼成功。并且將它編碼成了 UTF-8,打印出了原始的 byte 字符串,因?yàn)?ASCII 是 UTF-8 的一個(gè)子集。
最重要的事實(shí)之三:byte 和 unicode 都非常重要,你必須將兩個(gè)都處理好。你不能假設(shè)所有的字符串都是 byte,或者所有的字符串都是 unicode,你必須適當(dāng)?shù)倪\(yùn)用他們必要時(shí)轉(zhuǎn)換它們。
Python 3
我們看到了Python 2版本中有關(guān) Unicode 之痛?,F(xiàn)在我們看一下Python 3,在Python 2到Python 3中最重要的變化就是他們對(duì) Unicode 的處理。
跟Python 2類似,Python 3也有兩種類型,一個(gè)是 Unicode,一個(gè)是 byte 碼。但是他們有不同的命名。
現(xiàn)在你從普通文本轉(zhuǎn)換成str類型后存儲(chǔ)的是一個(gè) unicode,bytes類型存儲(chǔ)的是 byte串,你也可以通過(guò)一個(gè)b前綴來(lái)制造 byte串。
所以在Python 2中的str現(xiàn)在叫做bytes,而Python 2中的unicode現(xiàn)在叫做str。這比起Python 2中更容易理解,因?yàn)?Unicode 是你總想要存儲(chǔ)的內(nèi)容,而 bytes 字符串只有你在想要處理 byte的時(shí)候得到。
Python 3中對(duì) Unicode 支持的最大變化就是將會(huì)對(duì) byte 字符串的自動(dòng)解碼。如果你想要用一個(gè) byte 字符串和一個(gè) unicode 相連接接的話,你將會(huì)得到一個(gè)錯(cuò)誤,不管你包含的內(nèi)容是什么。
所有這些在Python 2中都將會(huì)有隱式的處理,而在Python 3中你將會(huì)得到一個(gè)錯(cuò)誤。
另外如果一個(gè) Unicode 字符串和 byte 字符串中包含的是相同的 ASCII 碼,Python 2中將認(rèn)為兩個(gè)是相等的,而在Python 3中不會(huì)。這樣做的結(jié)果是 Unicode 中的鍵不能找到 byte 字符串中的值,反之亦然,然而在Python 2中是可行的。
這樣徹底了改變了Python 3中的 Unicode 痛楚之源。在Python 2中,只要你使用 ASCII 數(shù)據(jù),那么混合 Unicode 和 byte 將會(huì)成功,而在Python 3會(huì)直接忽略數(shù)據(jù)而失敗。
這樣的話在Python 2中所遇到的,你認(rèn)為你的程序是正確的,但是最后發(fā)現(xiàn)由于一些特殊字符而失敗的錯(cuò)誤就會(huì)避免。
Python 3中,你的程序馬上就會(huì)產(chǎn)生錯(cuò)誤,所以即使你處理的是 ASCII 碼,那么你也必須處理 bytes 和 Unicode 之間的關(guān)系。
Python 3中對(duì)于 bytes 和 unicode 的處理非常嚴(yán)格,你被迫去處理這些事情。這曾經(jīng)引起爭(zhēng)議。
這樣處理的原因之一是對(duì)讀取文件的變化,Python對(duì)于讀取文件有兩種方式,一種是二進(jìn)制,一種是文本。在Python 2中,它只會(huì)影響到行尾符號(hào),甚至在 Unix 系統(tǒng)上的時(shí)候,基本沒(méi)有區(qū)別。
在Python 3中。這兩種模式將會(huì)返回不同的結(jié)果。當(dāng)你用文本模式打開一個(gè)文件時(shí)不管是你是用的r模式或者是它默認(rèn)的模式,讀取成的文件將會(huì)自動(dòng)轉(zhuǎn)碼成 unicode,你將會(huì)得到 str 對(duì)象。
如果你用二進(jìn)制模式打開一個(gè)文,在參數(shù)中輸入rb,那么從文件中讀取的數(shù)據(jù)將會(huì)是 bytes,對(duì)它們沒(méi)有任何處理。
隱式的對(duì) bytes 到 unicode 的處理使用的是locale.getpreferedencoding(),然而它有可能輸出你不想要的結(jié)果。比如,當(dāng)你讀取 hi_utf8.txt 時(shí),他被解碼成語(yǔ)言偏好中所設(shè)置的編碼方式,如果我們這些例子在 windows 中創(chuàng)建的話,那么就是cp1252。像ISO 8859-1、CP-1252這些可以得到任意的 byte 值,所以不會(huì)拋出UnicodeDecodeError,當(dāng)然也意味著他們會(huì)直接將數(shù)據(jù)解碼成CP-1252,制造出我們并不需要的垃圾信息。
為了文件讀取正確的話,你應(yīng)該指明想要的編碼。open函數(shù)現(xiàn)在已經(jīng)可以通過(guò)參數(shù)來(lái)指明編碼。
減輕痛苦
好,那么如何來(lái)減少這些痛苦?好消息是減輕痛苦的規(guī)則非常簡(jiǎn)單,在Python 2和Python 3中都比較適用。
正如我們?cè)谑聦?shí)一中所看到的,在你的程序中進(jìn)進(jìn)出出的只有bytes,但是在你的程序中你不必處理所有的 bytes。最好的策略是將輸入的 bytes 馬上解碼成 unicode。你在程序中均使用 unicode,當(dāng)在進(jìn)行輸出的時(shí)候,盡早將之編碼成 bytes。
制造一個(gè) Unicode 三明治, bytes 在外, Unicode 在內(nèi)。
要記?。河袝r(shí)候一些庫(kù)將會(huì)幫助你完成類似的事情。一些庫(kù)可能讓你輸入 unicode,輸出 unicode。它會(huì)幫你完成轉(zhuǎn)換的功能。比如 Django 在它的 json 模塊中提供 Unicode。
第二條規(guī)則:你需要知道你現(xiàn)在處理的是哪種類型的數(shù)據(jù),在你的程序中任何一個(gè)位置,你需要知道你處理的是 byte 串還是一個(gè) unicode 串。它不能是一種猜測(cè),而應(yīng)該被設(shè)計(jì)好。
另外,如果你有一個(gè) byte 串的話,如果你想對(duì)他進(jìn)行處理。那么你應(yīng)該知道他是怎樣的編碼。
在對(duì)你的代碼進(jìn)行 debug 的時(shí)候,不能僅僅將之打印出來(lái)來(lái)看它的類型。你應(yīng)該查看它的type,或者查看它repr之后的值來(lái)查看你的數(shù)據(jù)到底是什么類型。
我曾經(jīng)說(shuō)過(guò),你應(yīng)該了解你的 byte 字符串的編碼類型。好,這里要我講事實(shí)四:你不能通過(guò)檢查它來(lái)判斷這個(gè)字符串編碼的類型。你應(yīng)該通過(guò)其他途徑來(lái)了解。比如很多協(xié)議中將會(huì)指明編碼類型。這里我們給出 HTTP、HTML、XML、Python 源文件中的例子。你也可以通過(guò)預(yù)先的指定來(lái)了解編碼。比如數(shù)據(jù)源碼中可能會(huì)指明編碼。
有一些方式可以來(lái)猜測(cè)一些 bytes 的編碼類型。但是僅僅是猜測(cè)。能夠確定的唯一方式是通過(guò)其他方式。
這里是給出一些怪異的字符的編碼猜測(cè)。我們用UTF-8 便民店的一些字符,被不同的解碼方式解碼之后的輸出。你可以看見。有時(shí)候用不正確的解碼方式解碼可能會(huì)輸出正確,但是會(huì)輸出錯(cuò)誤的字符。你的程序不能告訴你這些解析錯(cuò)誤了。只有當(dāng)用戶察覺(jué)到的時(shí)候你才會(huì)發(fā)現(xiàn)錯(cuò)誤。
這是事實(shí)四的一個(gè)好例子:同樣的 bytes 流通過(guò)不同的解碼器是可以解碼的。而 bytes 本身不能指明它自己用的哪種編碼方式。
順便說(shuō)一下,這些垃圾信息的顯示只遵循一個(gè)規(guī)則,那就是亂碼。
不幸的是,bytes 流會(huì)根據(jù)自己的來(lái)源不同而進(jìn)行不同的編碼,有時(shí)候我們指明的編碼方式可能是錯(cuò)誤的。比如你有可能將一個(gè) HTML 從網(wǎng)上抓取下來(lái),HTTP 頭中指明編碼方式是8859-1,然而實(shí)際上的編碼確是UTF-8。
在一些情況下編碼方式的不匹配可能會(huì)產(chǎn)生亂碼,而有些時(shí)候,則會(huì)產(chǎn)生 UnicodeError。
不用說(shuō)。你應(yīng)該測(cè)試你的 Unicode 支持。為了這樣。你首先應(yīng)該在你的代碼中首先去先把 Unicode 來(lái)提取出。如果你只會(huì)說(shuō)英語(yǔ),這可能會(huì)有些困難。因?yàn)橛行?Unicode 數(shù)據(jù)會(huì)比較難以讀。幸運(yùn)的是,大部分時(shí)候一些復(fù)雜結(jié)構(gòu)的 Unicode 字符串還是比較具有可讀性的。
這里是一個(gè)例子。ASCII 文本中可以讀的文本,和倒置的文本。這些文本的一些有時(shí)候是一些青年人會(huì)粘貼到社交網(wǎng)絡(luò)中。
根據(jù)你的程序,你有可能在 Unicode 的道路中越挖越深。還有很多很多的細(xì)節(jié)我這里沒(méi)有解釋清楚??梢员簧婕暗健N覀兎Q之為事實(shí)五。因?yàn)槟悴槐厝?duì)此了解太詳細(xì)。
復(fù)習(xí)一下,我們有五個(gè)不可忽視的事實(shí):
- 程序中所有的輸入和輸出均為 byte
- 世界上的文本需要比 256 更多的符號(hào)來(lái)表現(xiàn)
- 你的程序必須處理 byte 和 unicode
- byte 流中不會(huì)包含編碼信息
- 指明的編碼有可能是錯(cuò)誤的
這是你在編程中保持 Unicode 清潔的三個(gè)建議:
- Unicode 三明治:盡可能的讓你程序處理的文本都為 Unicode 。
- 了解你的字符串。你應(yīng)該知道你的程序中,哪些是 Unicode,哪些是 byte,對(duì)于這些 byte 串,你應(yīng)該知道,他們的編碼是什么。
- 測(cè)試 Unicode 支持。使用一些奇怪的符號(hào)來(lái)測(cè)試你是否已經(jīng)做到了以上幾點(diǎn)。
如果你遵循以上建議的話,你將會(huì)寫出對(duì) Unicode 支持很好的代碼。不管 Unicode 中有多么不規(guī)整的編碼你的程序也不會(huì)掛掉。
其他一些你可能需要的資源
Joel Spolsky 編寫的The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)概括了 Unicode 的工作方式和原因。雖然沒(méi)有 Python 的內(nèi)容,但是比我解釋的詳細(xì)多了!
如果你需要處理一些語(yǔ)義上的Unicode字符問(wèn)題。那么unicodedata module也許會(huì)對(duì)你有些幫助。
如果你希望找一些Unicode來(lái)測(cè)試的話,網(wǎng)上各種的編碼文本計(jì)算器會(huì)對(duì)你很有幫助。