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 不支持超出范围的编码,并且我不知道是否有正在计划进行解决的项目。对不起,你已经到了我经验的极限了。

时时彩路数是什么 时时彩一个月盈利方案 时时彩规律破解 时时彩挂机方案是骗局 时时彩平台软件黑客
金诺时时彩软件免费版 时时彩后三杀垃圾复式 博定宝时时彩代理 神算时时彩破解版 哪个时时彩平台最靠谱
时时彩遗漏分析软件 1980时时彩注册平台 时时彩平台租用骗局 博众时时彩论坛 北京时时彩开奖走势图
时时彩公式杀号法 重庆时时彩微信群多少 天津时时彩走势图开奖 重庆时时彩2017计划群 黄金时时彩软件起尾巴
春天的故事 青海11选五开奖走势图 湖南幸运赛车历史开奖 pk10试用 黑龙江36选7开奖信息
山东十一选五走势图 江苏快3走势图老快3走势图 河南福利采票22选5最新开奖结果 快中彩网点 福彩上海快3开奖号码今天计划卐双色球预测最准确的人计划卐上海快3全部号码和值表计划卐上海快3结果
北京pk拾预测 福建31选7开奖公告 双色球中奖新闻 pk10预测软件 11选5杀号大师100准确
今日快乐十分开奖云南 凿壁借光的故事 北京赛车pk10大小走势图 安徽快三推荐号 中国福利彩票上海快3