Python Unicode編碼
當前實例版本:34 0 評論 1025 瀏覽 發布於:2013年11月26 07:50 編輯+新實例

字節流 vs Unicode對象

我們先來用Python定義一個字符串。當你使用string類型時,實際上會儲存一個字節串。

1
2
[  a ][  b ][  c ]="abc"
[97][98][99]="abc"

在這個例子裡,abc這個字符串是一個字節串。97.,98,,99是ASCII碼。Python 2.x版本的一個不足之處就是默認將所有的字符串當做ASCII來對待。不幸的是,ASCII在拉丁式字符集裡是最不常見的標準。

ASCII是用前127個數字來做字符映射。像windows-1252和UTF-8這樣的字符映射有相同的前127個字符。在你的字符串裡每個字節的值低於127的時候是安全的混合字符串編碼。然而作這個假設是件很危險的事情,下麵還將會提到。

當你的字符串裡有字節的值大於126的時候就會出現問題了。我們來看一個用windows-1252編碼的字符串。Windows-1252裡的字符映射是8位的字符映射,那麼總共就會有256個字符。前127個跟ASCII是一樣的,接下來的127個是由windows-1252定義的其他字符。

1
2
A windows-1252encoded string looks likethis:
[97] [98] [99] [150] ="abc–"

Windows-1252仍然是一個字節串,但你有冇有看到最後一個字節的值是大於126的。如果Python試著用默認的ASCII標準來解碼這個字節流,它就會報錯。我們來看當Python解碼這個字符串的時候會發生什麼:

1
2
3
4
5
6
7
>>> x="abc"+chr(150)
>>>printrepr(x)
'abc\x96'
>>> u"Hello"+x
Traceback (most recent call last):
  File"<stdin>", line1,in?
UnicodeDecodeError:'ASCII'codec can't decode byte0x96inposition3: ordinalnotinrange(128)

我們來用UTF-8來編碼另一個字符串:

1
2
3
A UTF-8encoded string looks likethis:
[97] [98] [99] [226] [128] [147] ="abc–"
[0x61] [0x62] [0x63] [0xe2]  [0x80] [0x93] ="abc-"

如果你拿起看你熟悉的Unicode編碼表,你會發現英文的破折號對應的Unicode編碼點為8211(0×2013)。這個值大於ASCII最大值127。大於一個字節能夠存儲的值。因為8211(0×2013)是兩個字節,UTF-8必須利用一些技巧告訴係統存儲一個字符需要三個字節。我們再來看當Python準備用默認的ASCII來編碼一個裡麵有字符的值大於126的UTF-8編碼字符串。

1
2
3
4
5
6
7
>>> x="abc\xe2\x80\x93"
>>>printrepr(x)
'abc\xe2\x80\x93'
>>> u"Hello"+x
Traceback (most recent call last):
  File"<stdin>", line1,in?
UnicodeDecodeError:'ASCII'codec can't decode byte0xe2inposition3: ordinalnotinrange(128)

你可以看到,Python一直是默認使用ASCII編碼。當它處理第4個字符的時候,因為它的值為226大於126,所以Python拋出了錯誤。這就是混合編碼所帶來的問題。

解碼字節流

在一開始學習Python Unicode 的時候,解碼這個術語可能會讓人很疑惑。你可以把字節流解碼成一個Unicode對象,把一個Unicode 對象編碼為字節流。

Python需要知道如何將字節流解碼為Unicode對象。當你拿到一個字節流,你調用它的“解碼方法來從它創建出一個Unicode對象。

你最好是儘早的將字節流解碼為Unicode。

1
2
3
4
5
6
7
8
9
>>> x="abc\xe2\x80\x93"
>>> x=x.decode("utf-8")
>>>printtype(x)
<type'unicode'>
>>> y="abc"+chr(150)
>>> y=y.decode("windows-1252")
>>>printtype(y)
>>>printx+y
abc–abc–

將Unicode編碼為字節流

Unicode對象是一個文本的編碼不可知論的代表。你不能簡單地輸出一個Unicode對象。它必須在輸出前被變成一個字節串。Python會很適合做這樣的工作,儘管Python將Unicode編碼為字節流時默認是適用ASCII,這個默認的行為會成為很多讓人頭疼的問題的原因。

1
2
3
4
5
6
7
>>> u=u"abc\u2013"
>>>printu
Traceback (most recent call last):
  File"<stdin>", line1,in<module>
UnicodeEncodeError:'ascii'codec can't encode character u'\u2013'inposition3: ordinalnotinrange(128)
>>>printu.encode("utf-8")
abc–

使用codecs模塊

codecs模塊能在處理字節流的時候提供很大幫助。你可以用定義的編碼來打開文件並且你從文件裡讀取的內容會被自動轉化為Unicode對象。

試試這個:

1
2
3
4
>>>importcodecs
>>> fh=codecs.open("/tmp/utf-8.txt","w","utf-8")
>>> fh.write(u"\u2013")
>>> fh.close()

它所做的就是拿到一個Unicode對象然後將它以utf-8編碼寫入到文件。你也可以在其他的情況下這麼使用它。

試試這個:

當從一個文件讀取數據的時候,codecs.open 會創建一個文件對象能夠自動將utf-8編碼文件轉化為一個Unicode對象。

我們接著上麵的例子,這次使用urllib流。

1
2
3
4
5
6
7
>>> stream=urllib.urlopen("http://www.google.com")
>>> Reader=codecs.getreader("utf-8")
>>> fh=Reader(stream)
>>>type(fh.read(1))
<type'unicode'>
>>> Reader
<classencodings.utf_8.StreamReader at0xa6f890>

單行版本:

1
2
>>> fh=codecs.getreader("utf-8")(urllib.urlopen("http://www.google.com"))
>>>type(fh.read(1))

你必須對codecs模塊十分小心。你傳進去的東西必須是一個Unicode對象,否則它會自動將字節流作為ASCII進行解碼。

1
2
3
4
5
6
7
8
9
10
>>> 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>", line1,in<module>
File"/usr/lib/python2.5/codecs.py", line638,inwrite
  returnself.writer.write(data)
File"/usr/lib/python2.5/codecs.py", line303,inwrite
  data, consumed=self.encode(object,self.errors)
UnicodeDecodeError:'ascii'codec can't decode byte0xe2inposition3: ordinalnotinrange(128)

哎呦我去,Python又開始用ASCII來解碼一切了。

 

將UTF-8字節流切片的問題

因為一個UTF-8編碼串是一個字節列表,len( )和切片操作無法正常工作。首先用我們之前用的字符串。

1
[97] [98] [99] [226] [128] [147]="abc–"

接下來做以下的:

1
2
3
>>> my_utf8="abc–"
>>>printlen(my_utf8)
6

神馬?它看起來是4個字符,但是len的結果說是6。因為len計算的是字節數而不是字符數。

1
2
>>>printrepr(my_utf8)
'abc\xe2\x80\x93'

現在我們來切分這個字符串。

1
2
>>> my_utf8[-1]# Get the last char
'\x93'

我去,切分結果是最後一字節,不是最後一個字符。

為了正確的切分UTF-8,你最好是解碼字節流創建一個Unicode對象。然後就能安全的操作和計數了。

1
2
3
4
5
6
7
>>> my_unicode=my_utf8.decode("utf-8")
>>>printrepr(my_unicode)
u'abc\u2013'
>>>printlen(my_unicode)
4
>>>printmy_unicode[-1]

當Python自動地編碼/解碼

在一些情況下,當Python自動地使用ASCII進行編碼/解碼的時候會拋出錯誤。

第一個案例是當它試著將Unicode和字節串合並在一起的時候。

1
2
3
4
>>> u"" + u"\u2019".encode("utf-8")
Traceback (most recent call last):
  File"<stdin>", line1,in<module>
UnicodeDecodeError:'ascii'codec can't decode byte0xe2inposition0:   ordinalnotinrange(128)

在合並列表的時候會發生同樣的情況。Python在列表裡有string和Unicode對象的時候會自動地將字節串解碼為Unicode。

1
2
3
4
>>>",".join([u"This string\u2019s unicode", u"This string\u2019s utf-8".encode("utf-8")])
Traceback (most recent call last):
  File"<stdin>", line1,in<module>
UnicodeDecodeError:'ascii'codec can't decode byte0xe2inposition11:  ordinalnotinrange(128)

或者當試著格式化一個字節串的時候:

1
2
3
4
>>>"%s\n%s"%(u"This string\u2019s unicode", u"This string\u2019s  utf-8".encode("utf-8"),)
Traceback (most recent call last):
  File"<stdin>", line1,in<module>
UnicodeDecodeError:'ascii'codec can't decode byte0xe2inposition11: ordinalnotinrange(128)

基本上當你把Unicode和字節串混在一起用的時候,就會導致出錯。

在這個例子裡麵,你創建一個utf-8文件,然後往裡麵添加一些Unicode對象的文本。就會報UnicodeDecodeError錯誤。

1
2
3
4
5
6
7
8
9
10
11
>>>buffer=[]
>>> fh=open("utf-8-sample.txt")
>>>buffer.append(fh.read())
>>> fh.close()
>>>buffer.append(u"This string\u2019s unicode")
>>>printrepr(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>", line1,in<module>
UnicodeDecodeError:'ascii'codec can't decode byte0xe2inposition9: ordinalnotinrange(128)

你可以使用codecs模塊把文件作為Unicode加載來解決這個問題。

1
2
3
4
5
6
7
8
9
10
11
12
>>>importcodecs
>>>buffer=[]
>>> fh=open("utf-8-sample.txt","r","utf-8")
>>>buffer.append(fh.read())
>>> fh.close()
>>>printrepr(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)
Thisfile’s got utf-8init
 
This string’sunicode

正如你看到的,由codecs.open 創建的流在當數據被讀取的時候自動地將比特串轉化為Unicode。

最佳實踐

1.最先解碼,最後編碼

2.默認使用utf-8編碼

3.使用codecs和Unicode對象來簡化處理

最先解碼意味著無論何時有字節流輸入,需要儘早將輸入解碼為Unicode。這會防止出現len( )和切分utf-8字節流發生問題。

最後編碼意味著隻有你打算將文本輸出到某個地方時,才把它編碼為字節流。這個輸出可能是一個文件,一個數據庫,一個socket等等。隻有在處理完成之後才編碼unicode對象。最後編碼也意味著,不要讓Python為你編碼Unicode對象。Python將會使用ASCII編碼,你的程序會崩潰。

默認使用UTF-8編碼意味著:因為UTF-8可以處理任何Unicode字符,所以你最好用它來替代windows-1252和ASCII。

codecs模塊能夠讓我們在處理諸如文件或socket這樣的流的時候能少踩一些坑。如果冇有codecs提供的這個工具,你就必須將文件內容讀取為字節流,然後將這個字節流解碼為Unicode對象。

codecs模塊能夠讓你快速的將字節流轉化為Unicode對象,省去很多麻煩。

解釋UTF-8

最後的部分是讓你能對UTF-8有一個入門的了解,如果你是個超級極客可以無視這一段。

利用UTF-8,任何在127和255之間的字節是特彆的。這些字節告訴係統這些字節是多字節序列的一部分。

1
2
Our UTF-8encoded string looks likethis:
[97] [98] [99] [226] [128] [147] ="abc–"

最後3字節是一個UTF-8多字節序列。如果你把這三個字節裡的第一個轉化為2進製可以看到以下的結果:

1
11100010

前3比特告訴係統它開始了一個3字節序列226,128,147。

那麼完整的字節序列。

1
111000101000000010010011

然後你對三字節序列運用下麵的掩碼。

1
2
3
4
1110xxxx 10xxxxxx 10xxxxxx
XXXX0010 XX000000 XX010011 Remove the X's
0010      000000  010011Collapse the numbers
0010000000010011         Get Unicode number0x2013,8211The"–"

這裡僅僅是關於UTF-8的一些入門的基本知識,如果想知道更多的細節,可以去看UTF-8的維基頁麵。