開發(fā)過(guò)程中總是會(huì)碰到string, unicode, ASCII, 中文字符等編碼的問(wèn)題, 每次碰到都要現(xiàn)搜, 很是浪費(fèi)時(shí)間, 于是這次狠下心, 一定要搞清楚python 的string和Unicode到底怎么回事.
基礎(chǔ)知識(shí)
我們都知道計(jì)算機(jī)只認(rèn)0和1, 要想在計(jì)算機(jī)顯示26個(gè)字母, 就要給他們一套映射規(guī)則: 計(jì)算機(jī)能認(rèn)得的符號(hào) --> 人類可讀的符號(hào). 這轉(zhuǎn)換的過(guò)程就是一套編碼規(guī)則.
- 字符集: 就是一套字符的集合(比如中文4000個(gè)漢字集合)
- 字符編碼: 一套法則, 能夠?qū)?/1和人類的語(yǔ)言之間進(jìn)行轉(zhuǎn)換的法則
最初字符集比較少, ASCII 碼就夠用了(一些控制符和26個(gè)字母), 隨著計(jì)算機(jī)的發(fā)展, 各國(guó)語(yǔ)言都有自己獨(dú)特的編碼, 漢字的編碼也不斷地?cái)U(kuò)展, 從GBK到 GB18030/DBCS. 這個(gè)時(shí)候Unicode應(yīng)運(yùn)而生.
Unicode就是為了統(tǒng)一各國(guó)各地區(qū)的編碼規(guī)則, 重新搞了一套包羅地球上所有文化, 符號(hào)的字符集! Unicode沒(méi)有編碼規(guī)則, 只是一套包含全世界符號(hào)的字符集. Unicode也不完美, 于是后續(xù)有了眾多UTF編碼(UTF-8, UTF-16).
總之搞清楚一件事情, 一個(gè)字符用了UTF-8編碼的, 就要用UTF-8去解碼, 不然就會(huì)出現(xiàn)亂碼.
文本處理
在python-2.x, 處理文本時(shí), 有string和unicode兩種類型
- str類型就是一串bytes, 這種類型跟C語(yǔ)言中處理string是非常相似的
- unicode就是一串unicode的數(shù)字映射(code point), 用于映射某個(gè)字符與一個(gè)unicode的對(duì)應(yīng)關(guān)系.
看看代碼出來(lái)是如何的:
>>> a = "簡(jiǎn)書"
>>> type(a)
<type 'str'>
>>> a
'\xe7\xae\x80\xe4\xb9\xa6'
>>> print a
簡(jiǎn)書
>>> u = u"簡(jiǎn)書"
>>> type(u)
<type 'unicode'>
>>> u
u'\u7b80\u4e66'
>>> print u
簡(jiǎn)書
從上面的代碼可以看到, a = "簡(jiǎn)書"是string類型, 可以看到a是一串 '\xe7\xae\x80\xe4\xb9\xa6' byte字符, 而u = u"簡(jiǎn)書"是一串\uxxxx的unicode數(shù)字, 通過(guò)print a 和 print u可以顯示出中文字符.
常見(jiàn)問(wèn)題#1
大家經(jīng)常犯的一個(gè)錯(cuò)誤就是混淆了unicode以及通過(guò)unicode編碼存儲(chǔ)在string里面的類型.
比如上面的例子中 u'\u7b80' 是unicode, '\xe7\xae\x80'是byte string, byte和unicode之間一一對(duì)應(yīng), 可以相互轉(zhuǎn)換, 轉(zhuǎn)換規(guī)則如下:
>>> '\xe7\xae\x80'.decode('utf-8')
u'\u7b80'
>>> print '\xe7\xae\x80'.decode('utf-8')
簡(jiǎn)
>>> u'\u7b80'.encode('utf-8')
'\xe7\xae\x80'
>>> print u'\u7b80'.encode('utf-8')
簡(jiǎn)
總結(jié)一下, 上面例子中
- unicode和byte都指
簡(jiǎn) - byte string 里面存儲(chǔ)的是unicode通過(guò)utf-8編碼后得到的bytes
- 所以byte string解碼(decode)后即可得到unicode
- unicode是byte string通過(guò)utf-8解碼后得到的
- unicode用utf-8編碼(encode)可以得到對(duì)應(yīng)的bytes
Note:
總而言之 Unicode ------編碼------> byte string
Unicode <-----解碼------- byte string
Unicode就像是加密傳輸中的明文, 可以用UTF-8, UTF-16, UTF-7, UTF-32等對(duì)unicode進(jìn)行加密, 最后解密還是要用回原本的加密方式來(lái)解密, 不然就解出亂碼啦.
常見(jiàn)問(wèn)題#2
對(duì)unicode或者byte string編碼解碼方向搞錯(cuò)
>>> u'\u7b80'.decode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/serena/Documents/data-pipeline/data-ci-sqlbuffet-env/lib/python2.7/encodings/utf_8.py", line 16, in decode
return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u7b80' in position 0: ordinal not in range(128)
>>> '\xe7\xae\x80'.encode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 0: ordinal not in range(128)
unicode應(yīng)該是進(jìn)行編碼的, 如果進(jìn)行decode, 是會(huì)出現(xiàn)UnicodeEncodeError 異常的. bytes string同理, 應(yīng)該進(jìn)行解碼, 如果硬要進(jìn)行編碼的話, 則拋出UnicodeDecodeError
常見(jiàn)問(wèn)題#3
API調(diào)用不一致的問(wèn)題. 在調(diào)用別人的API的時(shí)候, 需要看清楚是傳unicode還是byte string作為參數(shù). 因?yàn)榈谌降腁PI有的是支持unicode, 有的是byte string, 甚至有的兩種類型都支持. 這個(gè)時(shí)候要清楚自己傳進(jìn)去的參數(shù)是什么, 比如一些變量值是從http requests里面拉過(guò)來(lái)的, 這個(gè)時(shí)候你獲得的變量值很有可能是unicode類型(python requests get/post把返回值都轉(zhuǎn)成了unicode), 而如果第三方的API需要byte string, name就需要自己判斷一下并進(jìn)行轉(zhuǎn)換. 否則就會(huì)出現(xiàn)各種奇怪的UnicodeError
雖然python 社區(qū)規(guī)定了在所有的API中使用unicode, 但是少數(shù)一部分的API處于安全考慮還是要求使用byte string. 需要注意一下.
常見(jiàn)問(wèn)題#4
輸出類型不一致.
既然python社區(qū)推動(dòng)到處使用unicode, 那么我們只要在開發(fā)過(guò)程中全部都轉(zhuǎn)成unicode是不是就萬(wàn)事大吉了? 并不是, 當(dāng)你要輸出文本到terminal或者到文件, 這個(gè)文本必須是byte string類型的.
如果不是的話, python會(huì)隱式地幫你將unicode轉(zhuǎn)成string, python默認(rèn)采用ascii編碼,而中文編碼不在ascii編碼能夠表示的范圍之內(nèi),所以string無(wú)法將“你好”作為ascii編碼保存為str類型。
>>> string = unicode('你好', 'utf8')
>>> print string
你好
>>> log = open('/var/tmp/debug.log', 'w')
>>> log.write(string)
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)
所以當(dāng)你需要輸出的時(shí)候, 需要將你的unicode轉(zhuǎn)換成byte string再寫文件, 如果有中文的話, 要用'utf-8'或'GBK'等支持中文的編碼.
>>> string.encode('utf-8')
python 2.x的unicode & str其實(shí)搞清楚之后來(lái)來(lái)回回就是那些小問(wèn)題, 希望對(duì)大家有幫助.