# 9.4.?Unicode
Unicode 是一個系統,用來表示世界上所有不同語言的字符。當 Python 解析一個 XML 文檔時,所有的數據都是以unicode的形式保存在內存中的。
一會兒你就會了解,但首先,先看一些背景知識。
**歷史注解.?**在 unicode 之前,對于每一種語言都存在獨立的字符編碼系統,每個系統都使用相同的數字(0-255)來表示這種語言的字符。一些語言 (像俄語) 對于如何表示相同的字符還有幾種有沖突的標準;另一些語言 (像日語) 擁有太多的字符,需要多個字符集。在系統之間進行文檔交流是困難的,因為對于一臺計算機來說,沒有方法可以識別出文檔的作者使用了哪種編碼模式;計算機看到的只是數字,并且這些數字可以表示不同的東西。接著考慮到試圖將這些 (采用不同編碼的) 文檔存放到同一個地方 (比如在同一個數據庫表中);你需要在每段文本的旁邊保存字符的編碼,并且確保在傳遞文本的同時將編碼也進行傳遞。接著考慮多語言文檔,即在同一文檔中使用了不同語言的字符。(比較有代表性的是使用轉義符來進行模式切換;噗,我們處于俄語 koi8-r 模式,所以字符 241 表示這個;噗,現在我們處于 Mac 希臘語模式,所以字符 241 表示其它什么。等等。) 這些就是 unicode 被設計出來要解決的問題。
為了解決這些問題,unicode 用一個 2 字節數字表示每個字符,從 0 到 65535。\[8\] 每個 2 字節數字表示至少在一種世界語言中使用的一個唯一字符。(在多種語言中都使用的字符具有相同的數字碼。) 這樣就確保每個字符一個數字,并且每個數字一個字符。Unicode 數據永遠不會模棱兩可。
當然,仍然還存在著所有那些遺留的編碼系統的情況。例如,7 位 ASCII,它可以將英文字符存諸為從 0 到 127 的數值。(65 是大寫字母 “`A`”,97 是小寫字母 “`a`”,等等。) 英語有著非常簡單的字母表,所以它可以完全用 7 位 ASCII 來表示。像法語、西班牙語和德語之類的西歐語言都使用叫做 ISO-8859-1 的編碼系統 (也叫做“latin-1”),它使用 7 位 ASCII 字符表示從 0 到 127 的數字,但接著擴展到了 128-255 的范圍來表示像 n 上帶有一個波浪線(241),和 u 上帶有兩個點(252)的字符。Unicode 在 0 到 127 上使用了同 7 位 ASCII 碼一樣的字符表,在 128 到 255上同 ISO-8859-1 一樣,接著使用剩余的數字,256 到 65535,擴展到表示其它語言的字符。
在處理 unicode 數據時,在某些地方你可能需要將數據轉換回這些遺留編碼系統之一。例如,為了同其它一些計算機系統集成,這些系統期望它的數據使用一種特定的單字節編碼模式,或將數據打印輸出到一個不識別 unicode 的終端或打印機。或將數據保存到一個明確指定編碼模式的 XML 文檔中。
在了解這個注解之后,讓我們回到 Python上來。
從 2.0 版開始,Python 整個語言都已經支持 unicode。XML 包使用 unicode 來保存所有解析了的 XML 數據,而且你可以在任何地方使用 unicode。
## 例?9.13.?unicode 介紹
```
>>> s = u'Dive in'
>>> s
u'Dive in'
>>> print s
Dive in
```
| | |
| --- | --- |
| \[1\] | 為了創建一個 unicode 字符串而不是通常的 ASCII 字符串,要在字符串前面加上字母 “`u`”。注意這個特殊的字符串沒有任何非 ASCII 的字符。這樣很好;unicode 是 ASCII 的一個超集 (一個非常大的超集),所以任何正常的 ASCII 都可以以 unicode 形式保存起來。 |
| \[2\] | 在打印字符串時,Python 試圖將字符串轉換為你的默認編碼,通常是 ASCII 。(過會兒有更詳細的說明。) 因為組成這個 unicode 字符串的字符都是 ASCII 字符,打印結果與打印正常的 ASCII 字符串是一樣的;轉換是無縫的,而且如果你沒有注意到 `s` 是一個 unicode 字符串的話,你永遠也不會注意到兩者之間的差別。 |
## 例?9.14.?存儲非 ASCII 字符
```
>>> s = u'La Pe\xf1a'
>>> print s
Traceback (innermost last):
File "<interactive input>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in range(128)
>>> print s.encode('latin-1')
La Pe?a
```
| | |
| --- | --- |
| \[1\] | unicode 真正的優勢,理所當然的是它保存非 ASCII 字符的能力,例如西班牙語的 “`?`”(`n` 上帶有一個波浪線)。用來表示波浪線 n 的 unicode 字符編碼是十六進制的 `0xf1` (十進制的241),你可以像這樣輸入:`\xf1`。 |
| \[2\] | 還記得我說過 `print` 函數會嘗試將 unicode 字符串轉換為 ASCII 從而打印它嗎?嗯,在這里將不會起作用,因為你的 unicode 字符串包含非 ASCII 字符,所以 Python 會引發 `UnicodeError` 異常。 |
| \[3\] | 這兒就是將 unicode 轉換為其它編碼模式起作用的地方。`s` 是一個 unicode 字符串,但 `print` 只能打印正常的字符串。為了解決這個問題,我們調用 `encode` 方法 (它可以用于每個 unicode 字符串) 將 unicode 字符串轉換為指定編碼模式的正常字符串。我們向此函數傳入一個參數。在本例中,我們使用 `latin-1` (也叫 `iso-8859-1`),它包括帶波浪線的 n (然而缺省的 ASCII 編碼模式不包括,因為它只包含數值從 0 到 127 的字符)。 |
還記得我說過:需要從一個 unicode 得到一個正常字符串時,Python 通常默認將 unicode 轉換成 ASCII 嗎?嗯,這個默認編碼模式是一個可以定制的選項。
## 例?9.15.?`sitecustomize.py`
```
# sitecustomize.py
# this file can be anywhere in your Python path,
# but it usually goes in ${pythondir}/lib/site-packages/
import sys
sys.setdefaultencoding('iso-8859-1')
```
| | |
| --- | --- |
| \[1\] | `sitecustomize.py` 是一個特殊的腳本;Python 會在啟動的時候導入它,所以在其中的任何代碼都將自動運行。就像注解中提到的那樣,它可以放在任何地方 (只要 `import` 能夠找到它),但是通常它位于 Python 的`lib` 目錄的 `site-packages` 目錄中。 |
| \[2\] | 嗯,`setdefaultencoding` 函數設置默認編碼。Python 會在任何需要將 unicode 字符串自動轉換為正規字符串的地方,使用這個編碼模式。 |
## 例?9.16.?設置默認編碼的效果
```
>>> import sys
>>> sys.getdefaultencoding()
'iso-8859-1'
>>> s = u'La Pe\xf1a'
>>> print s
La Pe?a
```
| | |
| --- | --- |
| \[1\] | 這個例子假設你已經按前一個例子中的改動對 `sitecustomize.py` 文件做了修改,并且已經重啟了 Python。如果你的默認編碼還是 `'ascii'`,可能你就沒有正確設置 `sitecustomize.py` 文件,或者是沒有重新啟動 Python。默認的編碼只能在 Python 啟動的時候改變;之后就不能改變了。(由于一些我們現在不會仔細研究的古怪的編程技巧,你甚至不能在 Python 啟動之后調用 `sys.setdefaultencoding` 函數。仔細研究 `site.py`,并搜索 “`setdefaultencoding`” 去發現為什么吧。) |
| \[2\] | 現在默認的編碼模式已經包含了你在字符串中使用的所有字符,Python 對字符串的自動強制轉換和打印就不存在問題了。 |
## 例?9.17.?指定`.py`文件的編碼
如果你打算在你的 Python 代碼中保存非 ASCII 字符串,你需要在每個文件的頂端加入編碼聲明來指定每個 `.py` 文件的編碼。這個聲明定義了 `.py` 文件的編碼為 UTF-8:
```
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
```
現在,想想 XML 中的編碼應該是怎樣的呢?不錯,每一個 XML 文檔都有指定的編碼。重復一下,ISO-8859-1 是西歐語言存放數據的流行編碼方式。KOI8-R 是俄語流行的編碼方式。編碼――如果指定了的話――都在 XML 文檔的首部。
## 例?9.18.?`russiansample.xml`
```
<?xml version="1.0" encoding="koi8-r"?> <preface>
<title>Предисловие</title> </preface>
```
| | |
| --- | --- |
| \[1\] | 這是從一個真實的俄語 XML 文檔中提取出來的示例;它就是這本書俄語翻譯版的一部分。注意,編碼 `koi8-r` 是在首部指定的。 |
| \[2\] | 這些是古代斯拉夫語的字符,就我所知,它們用來拼寫俄語單詞“Preface”。如果你在一個正常文本編輯器中打開這個文件,這些字符非常像亂碼,因為它們使用了 `koi8-r` 編碼模式進行編碼,但是卻以 `iso-8859-1` 編碼模式進行顯示。 |
## 例?9.19.?解析 `russiansample.xml`
```
>>> from xml.dom import minidom
>>> xmldoc = minidom.parse('russiansample.xml')
>>> title = xmldoc.getElementsByTagName('title')[0].firstChild.data
>>> title
u'\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435'
>>> print title
Traceback (innermost last):
File "<interactive input>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in range(128)
>>> convertedtitle = title.encode('koi8-r')
>>> convertedtitle
'\xf0\xd2\xc5\xc4\xc9\xd3\xcc\xcf\xd7\xc9\xc5'
>>> print convertedtitle
Предисловие
```
| | |
| --- | --- |
| \[1\] | 我假設在這里你將前一個例子以 `russiansample.xml` 為名保存在當前目錄中。也出于完整性的考慮,我假設你已經刪除了 `sitecustomize.py` 文件,將缺省編碼改回到 `'ascii'`,或至少將 `setdefaultencoding` 一行注釋起來了。 |
| \[2\] | 注意 `title` 標記 (現在在 `title` 變量中,上面那一長串 Python 函數我們暫且跳過,下一節再解釋)――在 XML 文檔的 `title` 元素中的文本數據是以 unicode 保存的。 |
| \[3\] | 直接打印 title 是不可能的,因為這個 unicode 字符串包含了非 ASCII 字符,所以 Python 不能把它轉換為 ASCII,因為它無法理解。 |
| \[4\] | 但是,你能夠顯式地將它轉換為 `koi8-r`,在本例中,我們得到一個 (正常,非 unicode) 單字節字符的字符串 (`f0`, `d2`, `c5`,等等),它是初始unicode字符串中字符 `koi8-r` 編碼的版本。 |
| \[5\] | 打印 `koi8-r` 編碼的字符串有可能會在你的屏幕上顯示為亂碼,因為你的 Python IDE 將這些字符作為 `iso-8859-1` 的編碼進行解析,而不是 `koi8-r` 編碼。但是,至少它們能打印。 (并且,如果你仔細看,當在一個不支持 unicode 的文本編輯器中打開最初的 XML 文檔時,會看到相同的亂碼。Python 在解析 XML 文檔時,將它從 `koi8-r` 轉換到了unicode,你只不過是將它轉換回來。) |
總結一下,如果你以前從沒有看到過 unicode,倒是有些唬人,但是在 Python 處理 unicode 數據真是非常容易。如果你的 XML 文檔都是 7 位的 ASCII (像本章中的例子),你差不多永遠都不用考慮 unicode。Python 在進行解析時會將 XML 文檔中的 ASCII 數據轉換為 unicode,在任何需要的時候強制轉換回為 ASCII,你甚至永遠都不用注意。但是如果你要處理其它語言的數據,Python 已經準備好了。
## 進一步閱讀
* Unicode.org 是 unicode 標準的主頁,包含了一個簡要的[技術簡介](http://www.unicode.org/standard/principles.html)。
* Unicode 教程有更多關于如何使用 Python unicode 函數的例子,包括甚至在并不真的需要時如何將 unicode 強制轉換為 ASCII。
* PEP 263 涉及了何時、如何在你的`.py`文件中定義字符編碼的更多細節。
## Footnotes
\[8\] 這一點,很不幸_仍然_ 過分簡單了。現在 unicode 已經擴展用來處理古老的漢字、韓文和日文文本,它們有太多不同的字符,以至于 2 字節的 unicode 系統不能全部表示。但當前 Python 不支持超出范圍的編碼,并且我不知道是否有正在計劃進行解決的項目。對不起,你已經到了我經驗的極限了。
- 版權信息
- 第?1?章?安裝 Python
- 1.1.?哪一種 Python 適合您?
- 1.2.?Windows 上的 Python
- 1.3.?Mac OS X 上的 Python
- 1.4.?Mac OS 9 上的 Python
- 1.5.?RedHat Linux 上的 Python
- 1.6.?Debian GNU/Linux 上的 Python
- 1.7.?從源代碼安裝 Python
- 1.8.?使用 Python 的交互 Shell
- 1.9.?小結
- 第?2?章?第一個 Python 程序
- 2.1.?概覽
- 2.2.?函數聲明
- 2.3.?文檔化函數
- 2.4.?萬物皆對象
- 2.5.?代碼縮進
- 2.6.?測試模塊
- 第?3?章?內置數據類型
- 3.1.?Dictionary 介紹
- 3.2.?List 介紹
- 3.3.?Tuple 介紹
- 3.4.?變量聲明
- 3.5.?格式化字符串
- 3.6.?映射 list
- 3.7.?連接 list 與分割字符串
- 3.8.?小結
- 第?4?章?自省的威力
- 4.1.?概覽
- 4.2.?使用可選參數和命名參數
- 4.3.?使用 type、str、dir 和其它內置函數
- 4.4.?通過 getattr 獲取對象引用
- 4.5.?過濾列表
- 4.6.?and 和 or 的特殊性質
- 4.7.?使用 lambda 函數
- 4.8.?全部放在一起
- 4.9.?小結
- 第?5?章?對象和面向對象
- 5.1.?概覽
- 5.2.?使用 from _module_ import 導入模塊
- 5.3.?類的定義
- 5.4.?類的實例化
- 5.5.?探索 UserDict:一個封裝類
- 5.6.?專用類方法
- 5.7.?高級專用類方法
- 5.8.?類屬性介紹
- 5.9.?私有函數
- 5.10.?小結
- 第?6?章?異常和文件處理
- 6.1.?異常處理
- 6.2.?與文件對象共事
- 6.3.?for 循環
- 6.4.?使用 `sys.modules`
- 6.5.?與目錄共事
- 6.6.?全部放在一起
- 6.7.?小結
- 第?7?章?正則表達式
- 7.1.?概覽
- 7.2.?個案研究:街道地址
- 7.3.?個案研究:羅馬字母
- 7.4.?使用 {n,m} 語法
- 7.5.?松散正則表達式
- 7.6.?個案研究:解析電話號碼
- 7.7.?小結
- 第?8?章?HTML 處理
- 8.1.?概覽
- 8.2.?sgmllib.py 介紹
- 8.3.?從 HTML 文檔中提取數據
- 8.4.?BaseHTMLProcessor.py 介紹
- 8.5.?locals 和 globals
- 8.6.?基于 dictionary 的字符串格式化
- 8.7.?給屬性值加引號
- 8.8.?dialect.py 介紹
- 8.9.?全部放在一起
- 8.10.?小結
- 第?9?章?XML 處理
- 9.1.?概覽
- 9.2.?包
- 9.3.?XML 解析
- 9.4.?Unicode
- 9.5.?搜索元素
- 9.6.?訪問元素屬性
- 9.7.?Segue [9]
- 第?10?章?腳本和流
- 10.1.?抽象輸入源
- 10.2.?標準輸入、輸出和錯誤
- 10.3.?查詢緩沖節點
- 10.4.?查找節點的直接子節點
- 10.5.?根據節點類型創建不同的處理器
- 10.6.?處理命令行參數
- 10.7.?全部放在一起
- 10.8.?小結
- 第?11?章?HTTP Web 服務
- 11.1.?概覽
- 11.2.?避免通過 HTTP 重復地獲取數據
- 11.3.?HTTP 的特性
- 11.4.?調試 HTTP web 服務
- 11.5.?設置 User-Agent
- 11.6.?處理 Last-Modified 和 ETag
- 11.7.?處理重定向
- 11.8.?處理壓縮數據
- 11.9.?全部放在一起
- 11.10.?小結
- 第?12?章?SOAP Web 服務
- 12.1.?概覽
- 12.2.?安裝 SOAP 庫
- 12.3.?步入 SOAP
- 12.4.? SOAP 網絡服務查錯
- 12.5.?WSDL 介紹
- 12.6.?以 WSDL 進行 SOAP 內省
- 12.7.?搜索 Google
- 12.8.? SOAP 網絡服務故障排除
- 12.9.?小結
- 第?13?章?單元測試
- 13.1.?羅馬數字程序介紹 II
- 13.2.?深入
- 13.3.?romantest.py 介紹
- 13.4.?正面測試 (Testing for success)
- 13.5.?負面測試 (Testing for failure)
- 13.6.?完備性檢測 (Testing for sanity)
- 第?14?章?測試優先編程
- 14.1.?roman.py, 第 1 階段
- 14.2.?roman.py, 第 2 階段
- 14.3.?roman.py, 第 3 階段
- 14.4.?roman.py, 第 4 階段
- 14.5.?roman.py, 第 5 階段
- 第?15?章?重構
- 15.1.?處理 bugs
- 15.2.?應對需求變化
- 15.3.?重構
- 15.4.?后記
- 15.5.?小結
- 第?16?章?函數編程
- 16.1.?概覽
- 16.2.?找到路徑
- 16.3.?重識列表過濾
- 16.4.?重識列表映射
- 16.5.?數據中心思想編程
- 16.6.?動態導入模塊
- 16.7.?全部放在一起
- 16.8.?小結
- 第?17?章?動態函數
- 17.1.?概覽
- 17.2.?plural.py, 第 1 階段
- 17.3.?plural.py, 第 2 階段
- 17.4.?plural.py, 第 3 階段
- 17.5.?plural.py, 第 4 階段
- 17.6.?plural.py, 第 5 階段
- 17.7.?plural.py, 第 6 階段
- 17.8.?小結
- 第?18?章?性能優化
- 18.1.?概覽
- 18.2.?使用 timeit 模塊
- 18.3.?優化正則表達式
- 18.4.?優化字典查找
- 18.5.?優化列表操作
- 18.6.?優化字符串操作
- 18.7.?小結
- 附錄?A.?進一步閱讀
- 附錄?B.?五分鐘回顧
- 附錄?C.?技巧和竅門
- 附錄?D.?示例清單
- 附錄?E.?修訂歷史
- 附錄?F.?關于本書
- 附錄 G. GNU Free Documentation License
- G.0. Preamble
- G.1.?Applicability and definitions
- G.2.?Verbatim copying
- G.3.?Copying in quantity
- G.4.?Modifications
- G.5.?Combining documents
- G.6.?Collections of documents
- G.7.?Aggregation with independent works
- G.8.?Translation
- G.9.?Termination
- G.10.?Future revisions of this license
- G.11.?How to use this License for your documents
- 附錄 H. GNU 自由文檔協議
- H.0. 序
- H.1.?適用范圍和定義
- H.2.?原樣復制
- H.3.?大量復制
- H.4.?修改
- H.5.?合并文檔
- H.6.?文檔合集
- H.7.?獨立著作聚集
- H.8.?翻譯
- H.9.?終止協議
- H.10.?協議將來的修訂
- H.11.?如何為你的文檔使用本協議
- 附錄 I. Python license
- I.A. History of the software
- I.B.?Terms and conditions for accessing or otherwise using Python
- 附錄 J. Python 協議
- J.0. 關于譯文的聲明
- J.A.?軟件的歷史
- J.B.?使用 Python 的條款和條件