IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

XML 问题 #11: 重温 xml_pickle 和 xml_objectify

从开放源码和常识中学到的知识

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

David Mertz,Ph.D. (mertz@gnosis.cx), 修改人, Gnosis Software, Inc.

2001 年 6 月 01 日

自从作者 David Mertz 首次介绍了他对 XML 文档进行高级 Python 处理所使用的方便的实用程序以来,不断有用户和读者提出一些非常有益的改进和建议。本专栏介绍了一些对其模块套件的更改,以及有关模块的高级使用和定制方面的技巧。代码样本演示了 py_obj._XML 的属性、作为对象和列表处理的节点属性、 py_obj 神奇的属性行为等等。

我为 IBM 所写的专栏、教程和文章都有我自己的两重 -- 也可能是三重 -- 目的。首先,我很珍惜这个与其他程序员/开发人员分享我所掌握的知识的机会,并因此可能使您自己的任务变得更轻松一些。(写这些东西赚点钱也非常不错。)

新的可能性

我写作的另一个目的是将编程代码向公众域公布。在编写这些代码时,我的目的是说明常规编程概念,并围绕这些概念来剪裁代码。而同时还有另一个意图,就是向编程社区提供代码,个别开发人员可以为他们各自的目的直接利用这些代码。

在将代码公开的过程中,我从这些模块的用户那里得到了许多有价值的建议和增强性补丁。用户提出的大多数改进意见仅凭我自己是永远也想象不到的,并且有些改进意见具有非凡的见解。我在本文中所介绍的 xml_picklexml_objectify 的一些用途就是在写 XML 问题 #1XML 问题 #2(它们是最初讨论这些模块的专栏)时还根本不可能想到的。





回页首


对 xml_objectify 的增强

特别需要提一下,有一个变化曾一度长久地困扰着我。我选择的时机可能有些不走运。就在 2000 年 8 月我第一次创建 xml_objectifyxml_pickle 之后不久,PyXML 发行版经历了几个不兼容的版本。不久以后,Python 2.0 提出了自己的不很兼容的 XML 支持。在这个过程中,用户不断贡献出一些补丁程序来匹配当时的 Python XML 支持,但目前, xml_objectifyxml_pickle 都需要 Python 2.0+ 及其包含的 PyXML 软件包。假设在 XML 软件包方面有对 Python 2.0 的有效需求,我也允许对 Python 2.0 语法进行一些其它的更改。不能与 Python 1.5 向后兼容固然令人遗憾,但保留它实在难以做到。

我在“XML 问题 #2”中介绍的 xml_objectify 的一个特性是保持完整元素内容的(包括字符数据的子元素标记)这样一种特殊 _XML 属性。缺省行为仍然是 只有在对象包含字符级标记时才创建嵌套对象的 _XML 属性。不过现在,可以选择使用函数 keep_containers() 和值 ALWAYSMAYBENEVER 来更改这种行为。例如:


清单 1: 缺省 py_obj._XML 属性的创建
>>> xml_str = 
        
        '''<doc><p>Spam and eggs <b>are</b> tasty</p>
..                  <p>The Spanish Inquisition</p>
..                  <foot>Our weapon is fear</foot></doc>'''
>>> open(
        
        'test.xml',
        
        'w').write(xml_str)
>>> 
        
        
          
          from
        
         xml_objectify 
        
        
          
          import
        
         *
>>> py_obj = XML_Objectify(
        
        'test.xml').make_instance()
>>> py_obj.p[0].PCDATA
u
        
        'Spam and eggs  tasty'
>>> py_obj.p[0]._XML              
        
        # first <p> has <b> markup
u
        
        'Spam and eggs <b>are</b> tasty'
>>> py_obj.p[1].PCDATA
u
        
        'The Spanish Inquisition'
>>> py_obj.p[1]._XML              
        
        # second <p> has no markup
Traceback (most recent call last):
  File 
        
        "<stdin>", line 1, 
        
        
          
          in
        
         ?
AttributeError: 
        
        '_XO_p' instance has no attribute 
        
        '_XML'
      
      



清单 2: 更改 py_obj._XML 属性的创建
>>> _=keep_containers(ALWAYS)
>>> py_obj = XML_Objectify(
        
        'test.xml').make_instance()
>>> py_obj.p[1]._XML
u
        
        'The Spanish Inquisition'
>>> _=keep_containers(NEVER)
>>> py_obj = XML_Objectify(
        
        'test.xml').make_instance()
>>> py_obj.p[0]._XML
Traceback (most recent call last):
  File 
        
        "<stdin>", line 1, 
        
        
          
          in
        
         ?
AttributeError: 
        
        '_XO_p' instance has no attribute 
        
        '_XML'
      
      


xml_objectify 最强大的特性可能也是很微妙的一个。许多用户可能从来也不需要(甚至从没注意到)类的神奇行为。但却可以在手头上有一些特殊的用于确定“对象化”XML 节点行为的类。(原来的文章中提到了这点,但还是有必要实际看一下。)

在演示示例之前,我应该提醒您注意一些细节。为了避免第一个模块版本中草率的冲突, xml_objectify 现在对 XML 节点的类模板名称做了“大改动”。“抽象”节点类,或者 _XO_ ,本身有一些“神奇”的行为。在创建时 -- 无论是动态创建还是由程序员创建 -- 具体节点类的形式为 _XO_tagname (其中 <tagname> 是在对象化的 XML 文档中出现的标记)。

_XO_ 本身提供的“魔法”是 __getitem__()__len__() 方法。这两种方法可以让您在属性适合作为列表发挥作用的情况下象对待列表一样对待每个节点属性。但同时,可以在无需下标的情况下引用“独生子”节点。例如:


清单 3: 作为对象和对象列表的节点属性
>>> print type(py_obj.p), type(py_obj.foot)
<type 'list'> <type 'instance'>
>>> print py_obj.p[1].PCDATA, '...', py_obj.foot.PCDATA
The Spanish Inquisition ... Our weapon is fear
>>> for line in py_obj.p: print line.PCDATA,
..
Spam and eggs  tasty The Spanish Inquisition
>>> for line in py_obj.foot: print line.PCDATA,
..
Our weapon is fear
>>> map(lambda line: len(line.PCDATA), py_obj.foot)
[18]
>>> map(lambda line: len(line.PCDATA), py_obj.p)
[20, 23]


如果希望在程序中创建非常专有的节点类,还有可能有更多的神奇功能。基本上,可以让属性节点以 任何您希望的方式表现。


清单 4: 创建 py_obj 的神奇节点行为
>>> 
        
        
          
          import
        
         xml_objectify
>>> xml_str = 
        
        '''<buffet>
.. <plate><food>Steak</food><food>Potatoes</food></plate>
.. <plate><food>Corn</food><food>Broccoli</food></plate>
.. <buffet>'''
>>> open(
        
        'buffet.xml',
        
        'w').write(xml_str)
>>> 
        
        
          
          class
        
        
        
        
        
        
          
          plate
        
        (xml_objectify._XO_):
..    
        
        
          
          def
        
        
        
        
        
        
          
          eat
        
        (self):
..        
        
        
          
          for
        
         food 
        
        
          
          in
        
         self.food:
..            
        
        
          
          if
        
         food.PCDATA == 
        
        'Broccoli':
..                
        
        
          
          return
        
         
        
        "If I liked Broccoli, I might have to eat it!"
..        
        
        
          
          return
        
         
        
        "Yum!"
..
>>> xml_objectify._XO_plate = plate
>>> py_obj = XML_Objectify(
        
        'buffet.xml').make_instance()
>>> 
        
        
          
          print
        
         py_obj.plate[1].eat()
If I liked Broccoli, I might have to eat it!
>>> 
        
        
          
          print
        
         py_obj.plate[0].eat()
Yum!
      
      

请注意, xml_objectify._XO_plate 赋值的诀窍非常重要。为获得恰当的神奇行为,需要在该名称空间中存在那种相应的神奇的和经过大改动的类。

在我看来,如果能从 XML 文件中攫取一组数据,然后让一个非常自然的 Python 对象使用其自身的方法将这些数据作为自己的属性操作,那就太棒了。

EXPAT 技术

对于大型 XML 文档的操作,Costas Malamas 提出了一个非常宝贵的增强方法。到目前为止, xml_objectify 工作的唯一方法是创建一个 DOM 树,然后遍历该树,生成“Python 化”的对象。这对于小型 XML 文档来说还算有效,但对于大约 50k-100k 的文件来说,这种做法就开始变得异常缓慢了。似乎有一些复杂的排序工作在进行,而产生的 xml_objectify 对于大型文档来说根本无法使用。

幸运的是,Malamas 提供了一个基于 Python EXPAT 绑定、对 XML 文档进行语法分析的替代方法(EXPAT 是以 C 编写的高性能 XML 库)。虽然在 ExpatFactory 类中仍然有一些需要克服的困难(无法用于某些带有处理指令的文档),但在大多数情况下,这种新技术甚至可以快速处理大型 XML 文档。

EXPAT 技术受到其设计所施加的一些限制:明显丧失了 xml_obj_dom 属性(如果原先使用 xml_obj );并且再也没有 _XML 属性可供操作了。不过,后面一种限制的解决办法可能在今后出现。

选择使用哪种语法分析技术非常简单:


清单 5: 选择语法分析方法
>>> xml_obj = XML_Objectify('buffet.xml',EXPAT)
>>> xml_obj = XML_Objectify('buffet.xml',parser=DOM)

由于缺少指定的选项,缺省选项是使用旧的 DOM 技术,但今后的代码应该明确指定,以防缺省选项有所更改。 EXPATDOMxml_objectify 中的常数,它们只包含匹配字符串值。





回页首


对 xml_pickle 的增强

在希望保留 "unpickle" 对象的实例方法时,需要使用类似于 xml_objectify 的方式来填充 xml_pickle 名称空间。这听上去令人困惑,但以下代码可以帮助理解:


清单 6: 确保 unpickle 的 Python 对象有活力
>>> 
        
        
          
          import
        
         xml_pickle
>>> 
        
        
          
          class
        
        
        
        
        
        
          
          MyClass
        
        :
..    
        
        
          
          def
        
        
        
        
        
        
          
          DoIt
        
        (self):
..        
        
        
          
          print
        
         
        
        "Done!"
..
>>> o1 = MyClass()
>>> o1.attr1 = 
        
        'spam'
>>> xml_str = xml_pickle.XML_Pickler(o1).dumps()
>>> o2 = xml_pickle.XML_Pickler().loads(xml_str)
>>> o2.DoIt()
Traceback (most recent call last):
  File 
        
        "<stdin>", line 1, 
        
        
          
          in
        
         ?
AttributeError: 
        
        'MyClass' instance has no attribute 
        
        'DoIt'
>>> xml_pickle.MyClass = MyClass
>>> o2 = xml_pickle.XML_Pickler().loads(xml_str)
>>> o2.DoIt()
Done!
      
      


基本上,如果在开始所有 pickle/unpickle 过程之前将希望 pickle 的类放入 xml_pickle 名称空间中,可以恢复所有对象行为。不过,请注意,与使用 picklecPickle 一样,方法本身没有被 pickle(只是属性被 pickle)。对方法使用的是在运行时出现的类(自从上次 pickle 以来更接近当前的)。





回页首


循环引用和深度复制

Joshua Macy(在 Joe Kraska 的帮助下)消除了我在当初文章中指出的一个 xml_pickle 限制。在早期版本中, xml_pickle 并不尝试检查 pickle 对象中的循环引用。而且(出于相同的原因),早期版本将每个属性作为它实际 Python 对象的深层副本进行 pickle。如果 Python 对象有许多包含对同一对象引用的子结构,那么 pickle 的大小可能很快就会变得很大。而且,unpickle 的对象将包含多个对象,这些对象虽然有可能相等 ( a == a ),但与预 pickle 的原始对象并不等同 ( a is a )。

不管 Macy 的方法有什么好处,还需要将 DEEPCOPY 选项重新引入模块中。(非常棒的) refid / id 方案的主要问题是通用工具要使用它往往更困难了。非 Python 语言的用户可能希望简单地使用 xml_pickle 化的对象(更类似于分层的数据仓库而非完全动态的对象,但这没问题)。或者 pickle 对象的 XSLT 转换可能适用于某些特定目的。一段 pickle 过的节录显示了这个困难:


清单 7: 将 Python 对象作为 XML Pickle
<?xml version=
        
        "1.0"?>
<!DOCTYPE PyObject SYSTEM 
        
        "PyObjects.dtd">
<PyObject 
        
        
          
          class
        
        =
        
        "XML_Pickler" id=
        
        "1383532">
<attr name=
        
        "lst" type=
        
        "list" id=
        
        "1391340">
  <item type=
        
        "numeric" value=
        
        "1" />
  <item type=
        
        "numeric" value=
        
        "3.5" />
  <item type=
        
        "numeric" value=
        
        "2" />
  <item type=
        
        "numeric" value=
        
        "(4+7j)" />
</attr>
<attr name=
        
        "lst2" type=
        
        "ref" refid=
        
        "1391340" />
<attr name=
        
        "num" type=
        
        "numeric" value=
        
        "37" />
..
</PyObject>
      
      

您可以看出,要以常规方式(例如开发人员使用肉眼观察)区分出属性 lst2 要费一番工夫。人们必须拿掉 refid ,然后向回搜索,找到相应的 id 。实际上, type="ref" XML 属性的使用可能是个错误的选择。假设它 具有 refid XML 属性,那么仍然记录 type="list" ,就象 lst2 专指 lst 那样,那么事情就比较好理解了。当然,一旦执行了某些操作,就很难在不打破向后兼容性的情况下对它加以改进。

有着精密头脑的黑客可能会得到有关引用的一个小小警告。 id / refid 值是从相关对象的 Python id() 中形成的。这些值本来没有任何含义,但具有一种良好的特性,即在任何给定的运行时时刻,它们都是唯一的。 xml_pickle 不保证在不同运行时 pickle 的“相同”对象能够产生完全等同的 XML 文件( id 值几乎一定会有更改)。一般来说,特定的 id 值对于程序没有什么影响,但使用象密码散列或 CRC 这样的事物作为进程一部分的话,这就会是一个“陷阱”。

这种增强不需要太多的描述,但应用户要求,对“可 pickle”类型集合增加了 Numeric 数组。对于科学和数学 Python 用户,这些类型可以成为其对象的重要属性。 xml_pickle 做了一些智能的尝试,以确保 Numeric 在支持时能够出现。如果没有出现,它就退回到 array 模块。





回页首


不言而喻的 Python 理论

我在开发这些模块,或者只是在领导这些模块的开发中学到的一课是不言而喻的 Python 理论的价值: 首先保证正确,然后使它更快!

我们相当圆满地共同达到了后面一种要求。相对于 pickle 的对象大小来说,对 xml_pickle 的一些优化将其行为从 O(N^2) 带到可管理的 O(N)。这里的诀窍是,如果您执行得太频繁, str = str + "more stuff" 的效率就可能非常低下。使用 EXPAT 技术, xml_objectify 也同样很快速。我认为,如果我从早期起就过多地担心优化问题,就无法这么快地向外界提出我的见解,也无法领会到这些宝贵的建议的全部价值。

在能够创建更多象在本专栏中讨论的工具和库的同时,我希望听到有关开放源码软件开发的更多实际社会动向。这是一个很有意思的途径,我很想知道它最终将导致什么。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 作者 David Mertz 的 XML 模块位于: xml_objectify.pyxml_pickle.py

  • 如果想了解旧的,或者预先发行的模块的版本号,请浏览 gnosis.cx/download目录。这里有各种版本(名称中带有版本号)。没有版本号的模块通常是最新的“稳定”版本。另外,可以在该目录中找到许多其它好东西(全部公众域)。

  • 在 IBM developerWorks 上查找 Find David Mertz 有关 xml_picklexml_objectify 两篇最初的文章(2000 年 8 月): XML 问题 #1XML 问题 #2

  • 查找 David Mertz 以前的“XML 问题”专栏文章:
    • XML 问题 #3介绍 DocBook。
    • XML 问题 #4继续介绍如何使用 DocBook 构建旧的文档档案。
    • XML 问题 #5说明如何通过 XSLT 将 XML 文档转换成 HTML。
    • XML 问题 #6比较了半打 XML 编辑器,同时特别着眼于那些适合于有大量文本的文档的工具。
    • XML 问题 #7将 DTD 与 XML Schema 进行了权衡,并建议无论 W3C XML Schema 规范有多么成熟,开发者在什么情况下需要坚持使用 DTD。
    • XML 话题 #8讨论了由计算机科学家概念化出来的数据 模型的抽象理论是如何帮助我们开发特定的多表示数据流的。
    • XML 话题 #9 讨论了公众域 sql2dtdsql2xml 实用程序;这些实用程序可以不依赖 RDBMS 生成可移植 XML 结果集。
    • XML 话题 #10讨论了索引器是如何利用 XML 的层次节点结构的优势的。

  • 对照“IBM 认证开发者计划”的 XML 认证指导了解如何提高您的 XML 技能。

  • WebSphere Application Server 高级版 3.5 联机文档引用部分中的 Bone up on the XML DOM


关于作者

author

David Mertz 象从他自己老远(和从镜子里)拍下来的照片。可以通过 mertz@gnosis.cx与 David 联系,在 gnosis.cx/publish/上详细介绍了他的生活。非常欢迎对过去的、这一篇和将来的专栏文章提出建议和意见。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款