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