级别: 初级 David Mertz,博士 (mertz@gnosis.cx), 数据处理专家, Gnosis Software, Inc.
2000 年 8 月 01 日 在其有关“XML 问题”新专栏的第一部分 -- 也是作为在 XML 和 Python 之间创建更无缝的集成而不断进行探求的一部分 -- 的第一部分中,David Mertz 介绍了
xml_pickle 模块。Mertz 讨论了
xml_pickle 所涉及的设计目标和决定,并提供了一些很可能使用的清单。
什么是 XML? 什么是 Python?
XML 是标准通用标记语言 (SGML) 一个简化的方言。许多人都通过 HTML 熟悉了 SGML。XML 和 HTML 文档都是由文本和文本中穿插的标记所组成和构造;其中标记由尖括号括起。但 XML 包含许多标记系统,以使 XML 文档用于许多目的,包括:
- 杂志文章和用户文档
- 结构化数据的文件(例如 CSV 或 EDI 文件)
- 用于在程序之间进程间通信的消息
- 建筑图(例如 CAD 格式)
可以创建一组标记来捕捉您希望表示的任何种类的结构化信息,这可能就是为什么 XML 作为表示不同信息的一种通用标准越来越流行的原因。
Python 是一种可免费获得的、非常高级的解释型语言,由 Guido van Rossum 开发。它将清晰的语法与强大的(但是可选的)面向对象语义结合起来。Python 可用于一系列计算机平台,并提供平台间强大的可移植性。
项目介绍
有许多技术和工具可以用 Python 处理 XML 文档。(
参考资料部分提供了到两篇
developerWorks文章的链接,在这两篇文章中讨论了常规技术。并且还提供了到其它有关 XML/Python 主题的链接。)不过,现有的大多数 XML/Python 工具共有的一个问题是,它们主要是以 XML 为中心而不是以 Python 为中心。特定的构造和编码技术感觉在某个编程语言中很“自然”,而其它往往感觉象是从其它领域导入的。但在理想环境中,所有构造都天生适合各自的领域,并且各个领域无缝地合并在一起。如果是这样,程序员就可以变得非常创造性,而不只停留在让它工作就可以了。
我开始了一项在 XML 和 Python 之间创建更加无缝,更加自然的集成的研究项目。在本文和该专栏中的后续文章中,我将讨论该项目的一些目标、决定和限制;希望能为您提供一系列有用的模块和技术,来指明满足编程目标的更容易的方法。所有作为该项目一部分所创建的工具都将对公众领域公开。
Python 是一种语言,带有灵活的对象系统和一系列丰富的内置类型。Python 的这种丰富性对于该项目有利有弊。一方面,拥有以 Python 表示的大量本机设施使得大量 XML 结构表示起来更容易。另一方面,Python 的这些本机类型和结构在许多情况下也让人担心能否以 XML 表示本机 Python 对象。由于 XML 和 Python 之间存在不对称性,项目 -- 至少在最初时 -- 包含两个单独的模块:
xml_pickle 和
xml_objectify ,前者用于以 XML 表示的任意 Python 对象,后者用于将 XML 文档“本机”表示为 Python 对象。我们将在本文中讨论
xml_pickle 。
第 1 部分:xml_pickle
Python 的标准
pickle 模块已经提供了将 Python 对象串行化的简便方法,这种方法对于持久存储器或在网络上进行传输很有帮助。但在某些情况下,希望对带有一些不由
pickle 拥有的特性的格式执行串行化。即,格式:
- 是人类可读的
- 可以进行语法分析、控制,并且其对象由非 Python 语言导入
- 支持已存储串行化对象的确认
xml_pickle 在维护与
pickle 接口兼容性的同时提供了这些特性。不过,
xml_pickle 不是
pickle 通常意义上的替代,因为
pickle 保留了其自身的一些优点,例如较快的操作(特别是通过
cPickle 时)以及更为紧凑的对象表示。
使用 xml_pickle
尽管
xml_pickle 的接口与
pickle 的接口几乎相同,但还是有必要为那些不熟悉 Python 或
pickle 的人说明一下(非常简单)
xml_pickle 的用法。
要演示的 Python 代码 [xml_pickle]
import
xml_pickle
# import the module
# declare some classes to hold some attributes
class
MyClass1
:
pass
class
MyClass2
:
pass
# create a class instance, and add some basic data members to it
o = MyClass1()
o.num = 37
o.str =
"Hello World"
o.lst = [1, 3.5, 2, 4+7j]
# create an instance of a different class, add some members
o2 = MyClass2()
o2.tup = (
"x" ,
"y" ,
"z" )
o2.num = 2+2j
o2.dct = {
"this" :
"that" ,
"spam" :
"eggs" , 3.14:
"about PI" }
# add the second instance to the first instance container
o.obj = o2
# print an XML representation of the container instance
xml_string = xml_pickle.XML_Pickler(o).dumps()
print
xml_string
|
除了第一行和倒数第二行以外的所有代码对于使用对象实例来说都是常规 Python。它可能有些人为化和简单,但基本上您对实例数据成员(包括作为容器数据的嵌套实例,这是大多数复杂结构以 Python 构造的方式)所执行的每个操作都包含在上例中。Python 程序员只需要进行一个方法调用就能将他们的对象作为 XML 编码。
当然,一旦 "pickle" 了对象,往往希望以后能恢复它们(或者在其它地方使用)。假设上述几行代码已经运行,恢复对象表示很简单:
new_object = xml_pickle.XML_Pickler().loads(xml_string)
|
很明显,在实际情况中,您希望对所创建的 XML 文档执行的操作可能比只在运行时将它放在内存中要有趣得多。例如,将 XML 文档保存到磁盘(可能使用
XML_Pickler.dump() 方法),或者在通信通道上发送它。实际上,示例是打印到纸的,这是一种非常好的持久存储形式。
样本 Pyobjects.dtd 文档
运行上述样本代码将产生一些非常典型的 Python 对象的
xml_pickle 表示法特性。但下面的示例是我开发的手工编码的测试方案,具有包含文档类型中允许的每个 XML 结构、标记和属性的优点。这里创造了一些特定数据,但要想象数据所属的应用程序并不困难。
显示 xml_pickle 特性的样本代码
<?xml version="1.0"?>
<!DOCTYPE PyObject SYSTEM
"PyObjects.dtd">
<PyObject class="Automobile">
<attr
name="doors" type="numeric" value="4" />
<attr name="make"
type="string" value="Honda" />
<attr name="tow_hitch"
type="None" />
<attr name="prev_owners" type="tuple">
<item type="string" value="Jane Smith" />
<item
type="tuple">
<item type="string" value="John Doe" />
<item type="string" value="Betty Doe" />
</item>
<item type="string" value="Charles Ng" />
</attr>
<attr name="repairs" type="list">
<item type="string"
value="June 1, 1999: Fixed radiator" />
<item
type="PyObject" class="Swindle">
<attr name="date"
type="string" value="July 1, 1999" />
<attr
name="swindler" type="string" value="Ed's Auto" />
<attr
name="purport" type="string" value="Fix A/C" />
</item>
</attr>
<attr name="options" type="dict">
<entry>
<key type="string" value="Cup Holders" />
<val type="numeric" value="4" />
</entry>
<entry>
<key type="string" value="Custom Wheels" />
<val type="string" value="Chrome Spoked" />
</entry>
</attr>
<attr name="engine" type="PyObject"
class="Engine">
<attr name="cylinders" type="numeric"
value="4" />
<attr name="manufacturer" type="string"
value="Ford" />
</attr>
</PyObject>
|
非正式地,不难看出
PyObjects.dtd XML 文档的结构。(
参考资料中提供了正式的文档类型定义 (DTD)。)但 DTD 将消除任何不是非常明显的问题的歧义。
在研究样本 XML 文档时,您可以看到满足了三个规定的
xml_pickle 设计目标:
- 格式是人类可读的
- XML 表示法可以通过非
xml_pickle 的手段控制 -- 无论它们是与 Python/XML 模块无关的、其它编程语言中的 XML 库、XML 增强的编辑器和实用程序,还是只是简单的文本编辑器(如样本创建中使用的那个)
- Python 对象的 XML 表示可以使用标准 XML 确认程序和
PyObjects.dtd 来确认
符合 DTD 的
所有文档并且
只有符合 DTD 的文档才是有效 Python 对象的表示。
设计特点、告诫和限制
内容模型
Python 和 XML 的内容模型只不过在某些特定方面有所不同。一个重要差异是 XML 文档在格式上生来就是线性的。Python 对象属性 -- 还有 Python 字典 -- 没有明确的顺序(尽管实现细节创建的顺序很随意,例如散列的键)。在这方面,Python 对象模型与关系模型很接近;关系表中的各行没有“天然”的顺序,主键和辅键可以为表提供任何有意义的顺序。键总能够通过比较运算符来定序,但这种顺序与键的语义并没有关系。
XML 文档总是以特定顺序列出它的标记元素。顺序对于某些应用程序没有什么意义,但 XML 文档顺序总是存在的。将 Python 和 XML 中的键顺序重要性进行区分的结果是,由
xml_pickle 产生的文档不保证通过 "pickle"/"unpickle" 循环维护元素顺序。例如,手工准备的 PyObjects.dtd XML 文档,例如上面的那个,可以被 "unpickle" 到一个 Python 对象中。如果产生的对象又被 "pickle",
标记则很可能以和原始文档中不同的顺序出现。这是一个特性,不是错误,但您要理解这一现象。
限制
xml_pickle 的当前版本 (0.2) 中有几个已知的限制。一个潜在的严重缺陷是,没有人尝试捕捉复合/容器对象中的循环引用。如果对象属性往回引用到容器对象(或它的某些递归版本),
xml_pickle 将耗尽 Python 堆栈。循环引用往往表明在开始的对象设计中存在缺陷,但
xml_pickle 的更新版本肯定会尝试更智能地处理它们。
另一个限制是 XML 属性值的名称空间(例如
<attr name="123"> 中的 "123" )比有效 Python 变量和实例成员的名称空间更大。在 Python 名称空间以外手工创建的属性在实例的
.__dict__ 属性中存在奇怪的状态,但对于普通属性语法是不可访问的(例如 "obj.123" 是个语法错误)。这只是在使用非
xml_pickle 本身的方法来创建或修改 XML 文档时才有的问题。现在我还没有确定处理这个(有些模糊)问题的最好办法。
第三个限制是
xml_pickle 不能处理 Python 对象的所有属性。所有“常见”的数据成员(字符串、数、字典等)都能很好地 "pickle"。但不处理作为属性的实例方法、类和函数对象。使用
pickle 时,在 "pickle" 过程中忽略方法。如果类或函数对象作为属性存在,则出现 XMLPicklingError。这可能是正确的最终行为,但还不能下最后的结论。
设计选择
XML 文档设计中一个真正的歧义在于要选择何时使用标记属性以及,何时使用子元素。关于这一设计问题有不同的意见,XML 程序员常常会强烈地感觉到他们的观点是冲突的。这可能是在决定
xml_pickle 文档结构时最大的问题。
已确定的一个常规原则是,如果
事物生来就是“复数”的,应该由子元素表示。例如,Python 列表包含的项数没有限制,因此由一连串
子元素表示。另一方面,数是单数的事物(值可能大于 1,但在其中只有一样
事物)。在这种情况下,使用称为 "value" 的 XML 属性似乎更符合逻辑。真正困难的情况是在使用 Python 字符串时。它们基本上是
序列对象 -- 就象列表一样。但使用假设的
标记表示字符串中每一字符将破坏人类可读性的目标,并产生大量 XML 表示。决定是将字符串放在 XML "value" 属性中,如同对数进行的操作一样。不过,从美学的角度看,它们可能无法存在于一个标记容器中,特别在多行字符串的情况下。但由于在规范中没有其它“裸露”的 #PCDATA,所以这个结论似乎更合适。
部分原因是由于字符串是存储在 XML "value" 属性中 -- 但主要由于维护 XML 文档的语法性质 -- Python 字符串需要以“安全”的形式存储。在 Python 中可能出现一些不安全的事物。第一种类型是基本标记字符,例如大于和小于号。第二种类型是确定属性的引号和单引号。第三种类型是有疑问的 ASCII 值,例如 null 字符。考虑的一个可能性是将整个 Python 字符串以类似 base64 编码的格式进行编码。这可以让字符串“安全”,但对于人类是完全不可读的。因此决定使用混合方式。基本 XML 字符以 "&"、">" 或 """ 样式来转义。有疑问的 ASCII 值以 Python 样式转义,例如 "\000"。这种组合产生了人类可读的 XML 表示,但需要某些混合的方法来为已存储的字符串译码。
预期的用途
xml_pickle 往往对许多事物都有用,某些用户反馈表明它已进入初步用法。下面是一些构想。
- 可以使用现有的以 XML 为中心的工具(不一定要用 Python 编写)为 Python 对象的 XML 表示创建索引并加以分类。这提供了为 Python 对象数据库(例如 ZODB、PAOS 或者就是简单的
shelve )创建索引的一个现成方法。
- Python 对象的 XML 表示可以作为
其它OOP 语言的对象存储,特别是具有类似基本类型的那些。这是将要进行的一项工作。许多“重型”协议,例如 CORBA、XML-RPC 和 SOAP 都有一部分重叠的目标,但
xml_pickle 作为对象传送规范来说相当“轻量型”。
- 打印和显示 XML 文档的工具可以用于通过 XML 中间格式来提供方便的、人类可读的 Python 表示。
- Python 对象可以通过使用特定于 XML 的编辑器,或者就是文本编辑器来对它们的 XML 表示进行手工“调试”。一旦手工修改的对象被 "unpickle",就可以检查对程序操作进行编辑的结果。它提供了除其它现有的 Python 调试器和封装器以外的额外选项。
如果您发现了
xml_pickle 的其它用途,或者知道有什么增强,可以将它用在其它地方,请将您的反馈发送给我。
参考资料
关于作者
对本文的评价
|