了解Python編碼和Unicode

巴扎黑
發布: 2017-04-30 14:50:05
原創
1346 人瀏覽過

  我確定有很多關於Unicode和Python的說明,但為了方便自己的理解使用,我還是打算再寫一些關於它們的東西。

  位元組流 vs Unicode物件

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

[  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定義的其他字元。

A windows-1252 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 150 ] = "abc–"
登入後複製

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

>>> x = "abc" + chr(150)
>>> print repr(x)
'abc\x96'
>>> u"Hello" + x
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: &#39;ASCII&#39; codec can&#39;t decode byte 0x96 in position 3: ordinal not in range(128)
登入後複製

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

A UTF-8 encoded string looks like this:
[ 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編碼字串。

>>> x = "abc\xe2\x80\x93"
>>> print repr(x)
&#39;abc\xe2\x80\x93&#39;
>>> u"Hello" + x
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: &#39;ASCII&#39; codec can&#39;t decode byte 0xe2 in position 3: ordinal not in range(128)
登入後複製

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

  解碼位元組流

  在一開始學習Python Unicode 的時候,解碼這個術語可能會讓人很懷疑。你可以把位元組流解碼成一個Unicode對象,把一個Unicode 物件編碼成一個位元組流。

  Python需要知道如何將位元組流解碼為Unicode物件。當你拿到一個位元組流,你呼叫它的「解碼方法來從它建立出一個Unicode物件。

#   你最好是儘早的將位元組流解碼為Unicode。

>>> x = "abc\xe2\x80\x93"
>>> x = x.decode("utf-8")
>>> print type(x)
<type &#39;unicode&#39;>
>>> y = "abc" + chr(150)
>>> y = y.decode("windows-1252")
>>> print type(y)
>>> print x + y
abc–abc–
登入後複製

  將Unicode編碼為位元組流

  Unicode物件是一個文本的編碼不可知論的代表。你不能簡單地輸出一個Unicode物件。它必須在輸出前被變成一個位元組串。 Python會很適合做這樣的工作,儘管Python將Unicode編碼為位元組流時預設是適用ASCII,這個預設的行為會成為許多讓人頭痛的問題的原因。

>>> u = u"abc\u2013"
>>> print u
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: &#39;ascii&#39; codec can&#39;t encode character u&#39;\u2013&#39; in position 3: ordinal not in range(128)
>>> print u.encode("utf-8")
abc–
登入後複製

  使用codecs模組

#   codecs模組能在處理位元組流的時候提供很大幫助。你可以用定義的編碼來開啟檔案並且你從檔案裡讀取的內容會被自動轉換成Unicode物件。

  試試這個:

>>> import codecs
>>> 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流。

>>> stream = urllib.urlopen("http://www.google.com")
>>> Reader = codecs.getreader("utf-8")
>>> fh = Reader(stream)
>>> type(fh.read(1))
<type &#39;unicode&#39;>
>>> Reader
<class encodings.utf_8.StreamReader at 0xa6f890>
登入後複製

  單行版本:

>>> fh = codecs.getreader("utf-8")(urllib.urlopen("http://www.google.com"))
>>> type(fh.read(1))
登入後複製

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

>>> 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: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 3: ordinal not in range(128)
登入後複製

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

  將UTF-8位元組流切片的問題

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

[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"
登入後複製

  接下來做以下的:

>>> my_utf8 = "abc–"
>>> print len(my_utf8)
6
登入後複製

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

>>> print repr(my_utf8)
&#39;abc\xe2\x80\x93&#39;
登入後複製

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

>>> my_utf8[-1] # Get the last char
&#39;\x93&#39;
登入後複製

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

  為了正確的切分UTF-8,你最好是解碼位元組流建立一個Unicode物件。然後就能安全的操作和計數了。

>>> my_unicode = my_utf8.decode("utf-8")
>>> print repr(my_unicode)
u&#39;abc\u2013&#39;
>>> print len(my_unicode)
4
>>> print my_unicode[-1]
–
登入後複製

  當Python自動地編碼/解碼

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

  第一个案例是当它试着将Unicode和字节串合并在一起的时候。

>>> u"" + u"\u2019".encode("utf-8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 0:   ordinal not in range(128)
登入後複製

  在合并列表的时候会发生同样的情况。Python在列表里有string和Unicode对象的时候会自动地将字节串解码为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: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 11:  ordinal not in range(128)
登入後複製

  或者当试着格式化一个字节串的时候:

>>> "%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: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 11: ordinal not in range(128)
登入後複製

  基本上当你把Unicode和字节串混在一起用的时候,就会导致出错。

  在这个例子里面,你创建一个utf-8文件,然后往里面添加一些Unicode对象的文本。就会报UnicodeDecodeError错误。

>>> buffer = []
>>> fh = open("utf-8-sample.txt")
>>> buffer.append(fh.read())
>>> fh.close()
>>> buffer.append(u"This string\u2019s unicode")
>>> print repr(buffer)
[&#39;This file\xe2\x80\x99s got utf-8 in it\n&#39;, u&#39;This string\u2019s unicode&#39;]
>>> print "\n".join(buffer)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 9: ordinal not in range(128)
登入後複製

  你可以使用codecs模块把文件作为Unicode加载来解决这个问题。

>>> import codecs
>>> buffer = []
>>> fh = open("utf-8-sample.txt", "r", "utf-8")
>>> buffer.append(fh.read())
>>> fh.close()
>>> print repr(buffer)
[u&#39;This file\u2019s got utf-8 in it\n&#39;, u&#39;This string\u2019s unicode&#39;]
>>> 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 创建的流在当数据被读取的时候自动地将比特串转化为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之间的字节是特别的。这些字节告诉系统这些字节是多字节序列的一部分。

Our UTF-8 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"
登入後複製

  最后3字节是一个UTF-8多字节序列。如果你把这三个字节里的第一个转化为2进制可以看到以下的结果:

11100010
登入後複製

  前3比特告诉系统它开始了一个3字节序列226,128,147。

  那么完整的字节序列。

11100010 10000000 10010011
登入後複製

  然后你运用三字节序列的下面的掩码。

1110xxxx 10xxxxxx 10xxxxxx
XXXX0010 XX000000 XX010011 Remove the X&#39;s
0010       000000   010011 Collapse the numbers
00100000 00010011          Get Unicode number 0x2013, 8211 The "–"
登入後複製

  这是基本的UTF-8入门,如果想知道更多的细节,可以去看UTF-8的维基页面。

  原文链接: ERIC MORITZ   翻译: 伯乐在线 - 贱圣OMG

以上是了解Python編碼和Unicode的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板