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
为了创建一个 unicode 字符串而不是通常的 ASCII 字符串,要在字符串前面加上字母 “u”。注意这个特殊的字符串没有任何非 ASCII 的字符。这样很好;unicode 是 ASCII 的一个超集 (一个非常大的超集),所以任何正常的 ASCII 都可以以 unicode 形式保存起来。
在打印字符串时,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
unicode 真正的优势,理所当然的是它保存非 ASCII 字符的能力,例如西班牙语的 “?”(n 上带有一个波浪线)。用来表示波浪线 n 的 unicode 字符编码是十六进制的 0xf1 (十进制的241),你可以像这样输入:\xf1
还记得我说过 print 函数会尝试将 unicode 字符串转换为 ASCII 从而打印它吗?嗯,在这里将不会起作用,因为你的 unicode 字符串包含非 ASCII 字符,所以 Python 会引发 UnicodeError 异常。
这儿就是将 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') 
sitecustomize.py 是一个特殊的脚本;Python 会在启动的时候导入它,所以在其中的任何代码都将自动运行。就像注解中提到的那样,它可以放在任何地方 (只要 import 能够找到它),但是通常它位于 Pythonlib 目录的 site-packages 目录中。
嗯,setdefaultencoding 函数设置默认编码。Python 会在任何需要将 unicode 字符串自动转换为正规字符串的地方,使用这个编码模式。

例 9.16. 设置默认编码的效果

>>> import sys
>>> sys.getdefaultencoding() 
'iso-8859-1'
>>> s = u'La Pe\xf1a'
>>> print s                  
La Pe?a
这个例子假设你已经按前一个例子中的改动对 sitecustomize.py 文件做了修改,并且已经重启了 Python。如果你的默认编码还是 'ascii',可能你就没有正确设置 sitecustomize.py 文件,或者是没有重新启动 Python。默认的编码只能在 Python 启动的时候改变;之后就不能改变了。(由于一些我们现在不会仔细研究的古怪的编程技巧,你甚至不能在 Python 启动之后调用 sys.setdefaultencoding 函数。仔细研究 site.py,并搜索 “setdefaultencoding” 去发现为什么吧。)
现在默认的编码模式已经包含了你在字符串中使用的所有字符,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>
这是从一个真实的俄语 XML 文档中提取出来的示例;它就是这本书俄语翻译版的一部分。注意,编码 koi8-r 是在首部指定的。
这些是古代斯拉夫语的字符,就我所知,它们用来拼写俄语单词“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                        
Предисловие
我假设在这里你将前一个例子以 russiansample.xml 为名保存在当前目录中。也出于完整性的考虑,我假设你已经删除了 sitecustomize.py 文件,将缺省编码改回到 'ascii',或至少将 setdefaultencoding 一行注释起来了。
注意 title 标记 (现在在 title 变量中,上面那一长串 Python 函数我们暂且跳过,下一节再解释)――在 XML 文档的 title 元素中的文本数据是以 unicode 保存的。
直接打印 title 是不可能的,因为这个 unicode 字符串包含了非 ASCII 字符,所以 Python 不能把它转换为 ASCII,因为它无法理解。
但是,你能够显式地将它转换为 koi8-r,在本例中,我们得到一个 (正常,非 unicode) 单字节字符的字符串 (f0, d2, c5,等等),它是初始unicode字符串中字符 koi8-r 编码的版本。
打印 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 已经准备好了。

进一步阅读

Footnotes

[8] 这一点,很不幸仍然 过分简单了。现在 unicode 已经扩展用来处理古老的汉字、韩文和日文文本,它们有太多不同的字符,以至于 2 字节的 unicode 系统不能全部表示。但当前 Python 不支持超出范围的编码,并且我不知道是否有正在计划进行解决的项目。对不起,你已经到了我经验的极限了。

有玩江西时时彩的吗 时时彩元角分厘平台 时时彩助手安卓版 吉林时时彩投注技巧 hi时时彩走势图
大龙虾时时彩软件官网 重庆时时彩五星组10 时时彩微信充值 一诺服务365开票软件 多宝时时彩平台总代理
重庆时时彩大米娱乐 时时彩漏洞改单注入工具 时时彩平台制作软件 重庆时时彩霸主注册机 江西时时彩彩博士
时时彩开奖数据下载 天津时时彩百科 彩虹时时彩计划软件 天天时时彩手机版3 江西时时彩走试图