最近在 ptt 和 python.tw@ggroup 上看到一些關於 Python 下 Unicode 的討論,發現很多人在寫 Python 的時候,遇到 non-ASCII 字元就頭痛。Python 的 Unicode 支援非常直接易懂,透過 unicode 物件,non-ASCII 字元可以處理得非常漂亮。Python 程式員很幸福,標準程式庫已經把工具排在那裡,用就好了。

在 Python 中對 non-ASCII 字元的處理,大概可以分成兩類:

  1. 原始碼的編碼。
  2. non-ASCII 資料。

通常會遇到 1. 的問題,就是因為想對付 2. non-ASCII 資料。不過在 Python 原始碼檔案裡一旦出現了 non-ASCII 字元,就有多一點地方要注意。

字串與萬國碼字串

unicode 物件是 Python 處理 non-ASCII 字元/字串轉換工作的核心。unicode 物件常被我們指稱為「Unicode 字串」,其實是有誤導之嫌:

>>> ustr = u'中文' # unicode literal
>>> type(ustr)
<type 'unicode'>
>>> astr = 'ascii' # str literal
>>> type(astr)
<type 'str'>
>>> isinstance(ustr, str)
False
>>> isinstance(astr, unicode)
False

Python 的字串 (str) 其實不等於 Unicode 「字串」 (unicode),所以談到 Python 的 Unicode 字串的時候,要注意這個因循而來的名詞,所指涉的並不是「字串」,而是另一種物件。根據 PLR-4.8.2 的說明,unicode 物件內部會用 UCS-2 或 UCS-4 儲存 Unicode 字元的 codepoint。不過單以使用的角度來說,unicode 用哪種方式儲存 Unicode 字元並不重要,重要的是不要把 unicode 和一般字串 str 混為一談。

雖然 unicode 不是 str,但兩者仍然高度相關:

>>> unicode.__base__
<type 'basestring'>
>>> str.__base__
<type 'basestring'>

在 Python 這樣的設計之下,使用者可以把 unicode 當作一種超級容器,用來表示各種各樣的字元。

編碼互換

unicodestr 放在手上,我們能拿來作什麼用?假設我們從檔案裡面把資料讀了出來,這些資料在 Python 裡一定是表現為字串的形式:

>>> data = open('whatever.file').read()
>>> type(data)
<type 'str'>

而要輸出的時候,Python 向你要的也是字串 (str)。不管字元處於哪種編碼之下,一定是存成 str,不會是 unicode。大部分的電腦系統都是以 byte 為單位來儲存資料,因此這是當然的結果。

考慮一下轉碼問題。假設有 100 種編碼要互換,最直覺的作法可能是為每一種編碼寫出對另外 99 種編碼的轉換器。很直覺,但非常辛苦。總共會需要寫 99*100 個不同的轉換器。稍微想一下以後,就會發現如果能定義出另一種新的編碼,包含所有 100 種編碼的字元,只要所有的編碼都可以和這種超級編碼互換,那麼作出 100*2 個轉換器就可以了。Unicode 是我們的萬能編碼,而 Python 的 unicode 物件善用它的能力,為轉碼問題提供了良好解答。

在 Python 裡實作轉碼的時候,要記得 unicode 是「超級容器」。雖然它的底層必定要用某種編碼 (UCS-2 或 UCS-4) 來表示字元,但在使用時,它被視為「沒有編碼」。所以任何編碼之下的字串,都可以「解碼」成 unicode

>>> ustr = big5str.decode('Big5')
>>> type(ustr)
<type 'unicode'>

解碼成 unicode 之後,經過「編碼」,就可以轉成另一種編碼下的字串:

>>> gbstr = ustr.encode('GB2312')
>>> type(gbstr)
<type 'str'>

原始碼檔案裡的 non-ASCII

Python 預設接受的原始碼必須是以 ASCII 編碼的。也就是說,除了正常的英文和符號之外,奇奇怪怪的東西不該出現,譬如說中文。如果我們想在 Python 程式裡顯示 non-ASCII 字元,最正當的作法是把這些字元放在其它檔案裡,在需要的時候讀出來,再根據環境進行編碼、顯示。不過對不需要嚴謹結構的小程式來說,特別去作出一個資源檔顯然太過牛刀,因此 Python 也允許程式員對原始碼的編碼作一些調整。

在原始碼檔案的檔頭加上:

# -*- coding: UTF-8 -*-

就可以讓 Python 編譯器視該檔案為 UTF-8 編碼。可不可以用其它編碼?最好不要,這個世界已經夠複雜了。

終端機的編碼

如果所有的文字資料都是 unicode 物件,Python 很聰明,會根據所偵測到的終端機編碼,自己把 unicode 物件轉碼成合適的 str, 然後輸出到終端機上 (sys.stdoutsys.stderr)。問題是在於 str 型態的文字資料,Python 完全不知道該拿它怎麼辦,只好當它和終端機的編碼一樣。如果不幸其實不一樣的時候,我們所謂的亂碼就出現了;終端機拿到其它編碼的字串,顯示出來的只能是一團亂。

所以,寫 Python 程式可以很簡單地避免亂碼問題:把所有的文字都放在 unicode 裡面, 或是至少所有要顯示的文字都存成 unicode

Posted by yungyuc at 04:32, 0 comment, 0 trackback.
Navigate
Add a trackback
Add a comment

Your name. (required)

Your personal website. (optional)

Your email address. Will not show in page. (suggested, but optional)

Text format is "Plain Text".

Enter "KORnN"
© hover year to navigate month: powered by django