Python处理XML差吗?
编辑
在这个问题中提到的“对XML不太好”这个说法引起了一些争议,所以我想先明确一下我在这个上下文中对这个词的定义:如果一个语言对标准XML API的支持很差,导致必须使用特定语言的API,而在这个API中,命名空间似乎是个附带的东西,那么我就会认为这个语言在使用XML方面不如其他主流语言好。“对XML不太好”只是对这些情况的简化说法,我觉得这样描述是合理的。正如我将要描述的,我最初使用Python的体验让我对它是否符合这些条件产生了疑虑;但因为我总体上对Python的体验还是很积极的,所以我觉得可能是我遗漏了什么,这也促使了我提出这个问题。
我想用Python做一些非常简单的XML处理。我最开始希望能重用我对标准W3C DOM API的知识,结果发现xml.dom和xml.dom.minidom模块很好地支持了这些API。然而,不幸的是,序列化却出现了问题,原因如下:
- xml.dom没有自带序列化工具
- 包含xml.dom序列化工具的PyXML库已经不再维护,并且
- minidom不支持命名空间的序列化,尽管API中支持命名空间
我查看了这里其他W3C类似库的列表:
http://wiki.python.org/moin/PythonXml#W3CDOM-likelibraries
我发现许多其他库,比如4Suite和libxml2dom,也没有得到维护。
另一方面,itools乍一看似乎在维护,但似乎没有可用的Ubuntu/Debian包,所以部署和维护会比较困难。
到这个时候,尝试在我的Python应用中使用W3C DOM API似乎走到了死胡同,我开始考虑使用ElementTree API。但我觉得eTree API对命名空间的支持非常糟糕,每次创建特定命名空间的元素时都需要使用字符串拼接:
http://lxml.de/tutorial.html#namespaces
所以,我的问题是,我是不是漏掉了什么,还是说Python对XML(特别是W3C DOM)的支持确实很差?
编辑
以下是一些更具体的问题,回答这些问题对我会很有帮助:
- Python对W3C DOM的支持是否合理?
- 如果不使用
xml.dom
,你会用例如etree
来代替W3C DOM吗? - 如果是的话,哪个库最好,如何解决API中关于命名空间的问题?
- 如果你使用W3C DOM,是否知道有哪个库实现了支持命名空间的序列化?
3 个回答
Python在处理XML方面非常出色,我认为lxml是我用过的最好的XML库,它功能强大,而且比DOM简单得多。命名空间的处理需要一些适应,但我觉得这也是lxml保持简单的一种好方法。
编辑
在重新阅读问题后,作者到底是指Python对象的序列化,还是仅仅指DOM树,这一点并不明确。我下面的回答假设是前者。
XML序列化是一个完全不同的问题。个人认为这并不是特别重要。大多数XML序列化工具生成的输出通常与特定语言或运行环境紧密相关,这样就失去了使用开放格式的意义。我知道有一些通用的XML序列化方案,但Python提供了两种在95%的情况下更好的解决方案:Pickling和JSON。
如果你的应用不需要与非Python系统共享对象,Pickling是你能找到的最快、最强大的序列化解决方案。JSON在解析和生成上都要快得多,而且比XML更容易使用。虽然JSON也有很多限制,但通常绕过这些限制要比处理XML的麻烦要简单得多。
还有很多其他的序列化格式,根据应用的不同,我会推荐它们优先于XML(例如:Google Protocol Buffers或YAML)。
另外,别忘了SAX。事件驱动的解析器只适合读取XML,但我发现它在某些问题上仍然是最好的解决方案。
我觉得eTree API处理命名空间的方式非常糟糕,每次创建特定命名空间的元素时都需要拼接字符串,这样很麻烦。
在.NET的 System.Linq.Xml
DOM中,创建一个带命名空间的元素的方法是:
XNamespace ns = "my-namespace";
XElement elm = new XElement(ns + "foo");
在lxml中创建一个命名空间元素的方法是:
ns = "{my-namespace}"
elm = etree.Element(ns + "foo")
我并没有看到什么糟糕的地方。实际上,.NET API的开发者为了让他们的API能够像lxml
一样直观地处理命名空间,做了很多努力,甚至创建了支持运算符重载的基类。
我不明白为什么这比W3C DOM要求你使用不同的方法来创建带命名空间和不带命名空间的元素要糟糕。
我觉得Python处理XML的能力还是挺不错的。市面上有很多不同的库可以选择,这说明了这一点——你有很多选择。如果你发现某些库缺少你想用的功能,欢迎你自己动手修复一下!
我个人使用的是DOM和lxml.etree(etree的速度真的很快)。不过,我也理解你在处理命名空间时的困扰。为此,我写了一个简单的辅助函数来解决这个问题:
DEFAULT_NS = "http://www.domain.org/path/to/xml"
def add_xml_namespace(path, namespace=DEFAULT_NS):
"""Adds namespaces to an XPath-ish expression path for etree
Test simple expression:
>>> add_xml_namespace('image/namingData/fileBaseName')
'{http://www.domain.org/path/to/xml}image/{http://www.domain.org/path/to/xml}namingData/{http://www.domain.org/path/to/xml}fileBaseName'
More complicated expression
>>> add_xml_namespace('.//image/*')
'.//{http://www.domain.org/path/to/xml}image/*'
>>> add_xml_namespace('.//image/text()')
'.//{http://www.domain.org/path/to/xml}image/text()'
"""
pattern = re.compile(r'^[A-Za-z0-9-]+$')
tags = path.split('/')
for i in xrange(len(tags)):
if pattern.match(tags[i]):
tags[i] = "{%s}%s" % (namespace, tags[i])
return '/'.join(tags)
我这样使用它:
from lxml import etree
from utilities import add_xml_namespace as ns
tree = etree.parse('file.xml')
node = tree.get_root().find(ns('root/group/subgroup'))
# etc.
如果你事先不知道命名空间,可以从根节点提取出来:
tree = etree.parse('file.xml')
root = tree.getroot().tag
namespace = root[1:root.index('}')]
ns = lambda path: add_xml_namespace(path, namespace)
...
补充说明:这里确实需要做一些工作,但在处理XML时,工作是必不可少的。这不是Python的问题,而是XML本身的问题。