深入解读Python解析XML的几种方式 及 ElementTree模块的使用

Python解析XML的几种方式详解

这篇文章主要为大家详细介绍了深入解读Python解析XML的几种方式,以ElementTree模块为例,演示具体使用方法和场景,感兴趣的小伙伴们可以参考一下

在XML解析方面,Python贯彻了自己“开箱即用”(batteries included)的原则。在自带的标准库中,Python提供了大量可以用于处理XML语言的包和工具,数量之多,甚至让Python编程新手无从选择。

本文将介绍深入解读利用Python语言解析XML文件的几种方式,并以笔者推荐使用的ElementTree模块为例,演示具体使用方法和场景。文中所使用的Python版本为2.7。

一、什么是XML?

XML是可扩展标记语言(Extensible Markup Language)的缩写,其中的 标记(markup)是关键部分。您可以创建内容,然后使用限定标记标记它,从而使每个单词、短语或块成为可识别、可分类的信息。

标记语言从早期的私有公司和政府制定形式逐渐演变成标准通用标记语言(Standard Generalized Markup Language,SGML)、超文本标记语言(Hypertext Markup Language,HTML),并且最终演变成 XML。XML有以下几个特点。

  • XML的设计宗旨是传输数据,而非显示数据。
  • XML标签没有被预定义。您需要自行定义标签。
  • XML被设计为具有自我描述性。
  • XML是W3C的推荐标准。

目前,XML在Web中起到的作用不会亚于一直作为Web基石的HTML。 XML无所不在。XML是各种应用程序之间进行数据传输的最常用的工具,并且在信息存储和描述领域变得越来越流行。因此,学会如何解析XML文件,对于Web开发来说是十分重要的。

二、有哪些可以解析XML的Python包?

Python的标准库中,提供了6种可以用于处理XML的包。

xml.dom

xml.dom实现的是W3C制定的DOM API。如果你习惯于使用DOM API或者有人要求这这样做,可以使用这个包。不过要注意,在这个包中,还提供了几个不同的模块,各自的性能有所区别。

DOM解析器在任何处理开始之前,必须把基于XML文件生成的树状数据放在内存,所以DOM解析器的内存使用量完全根据输入资料的大小。

xml.dom.minidom

xml.dom.minidom是DOM API的极简化实现,比完整版的DOM要简单的多,而且这个包也小的多。那些不熟悉DOM的朋友,应该考虑使用xml.etree.ElementTree模块。据lxml的作者评价,这个模块使用起来并不方便,效率也不高,而且还容易出现问题。

xml.dom.pulldom

与其他模块不同,xml.dom.pulldom模块提供的是一个“pull解析器”,其背后的基本概念指的是从XML流中pull事件,然后进行处理。虽然与SAX一样采用事件驱动模型(event-driven processing model),但是不同的是,使用pull解析器时,使用者需要明确地从XML流中pull事件,并对这些事件遍历处理,直到处理完成或者出现错误。

  • pull解析(pull parsing)是近来兴起的一种XML处理趋势。此前诸如SAX和DOM这些流行的XML解析框架,都是push-based,也就是说对解析工作的控制权,掌握在解析器的手中。

xml.sax

xml.sax模块实现的是SAX API,这个模块牺牲了便捷性来换取速度和内存占用。SAX是Simple API for XML的缩写,它并不是由W3C官方所提出的标准。它是事件驱动的,并不需要一次性读入整个文档,而文档的读入过程也就是SAX的解析过程。所谓事件驱动,是指一种基于回调(callback)机制的程序运行方法。

xml.parser.expat

xml.parser.expat提供了对C语言编写的expat解析器的一个直接的、底层API接口。expat接口与SAX类似,也是基于事件回调机制,但是这个接口并不是标准化的,只适用于expat库。

expat是一个面向流的解析器。您注册的解析器回调(或handler)功能,然后开始搜索它的文档。当解析器识别该文件的指定的位置,它会调用该部分相应的处理程序(如果您已经注册的一个)。该文件被输送到解析器,会被分割成多个片断,并分段装到内存中。因此expat可以解析那些巨大的文件。

xml.etree.ElementTree(以下简称ET)

xml.etree.ElementTree模块提供了一个轻量级、Pythonic的API,同时还有一个高效的C语言实现,即xml.etree.cElementTree。与DOM相比,ET的速度更快,API使用更直接、方便。与SAX相比,ET.iterparse函数同样提供了按需解析的功能,不会一次性在内存中读入整个文档。ET的性能与SAX模块大致相仿,但是它的API更加高层次,用户使用起来更加便捷。

笔者建议,在使用Python进行XML解析时,首选使用ET模块,除非你有其他特别的需求,可能需要另外的模块来满足。

  • 解析XML的这几种API并不是Python独创的,Python也是通过借鉴其他语言或者直接从其他语言引入进来的。例如expat就是一个用C语言开发的、用来解析XML文档的开发库。而SAX最初是由DavidMegginson采用java语言开发的,DOM可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构,可以应用于任何编程语言。

下面,我们以ElementTree模块为例,介绍在Python中如何解析lxml。

三、利用ElementTree解析XML

Python标准库中,提供了ET的两种实现。一个是纯Python实现的xml.etree.ElementTree,另一个是速度更快的C语言实现xml.etree.cElementTree。请记住始终使用C语言实现,因为它的速度要快很多,而且内存消耗也要少很多。如果你所使用的Python版本中没有cElementTree所需的加速模块,你可以这样导入模块:

?

1

2

3

4

try:

  import xml.etree.cElementTree as ET

except ImportError:

  import xml.etree.ElementTree as ET

如果某个API存在不同的实现,上面是常见的导入方式。当然,很可能你直接导入第一个模块时,并不会出现问题。请注意,自Python 3.3之后,就不用采用上面的导入方法,因为ElemenTree模块会自动优先使用C加速器,如果不存在C实现,则会使用Python实现。因此,使用Python 3.3+的朋友,只需要import xml.etree.ElementTree即可。

1、将XML文档解析为树(tree)

我们先从基础讲起。XML是一种结构化、层级化的数据格式,最适合体现XML的数据结构就是树。ET提供了两个对象:ElementTree将整个XML文档转化为树,Element则代表着树上的单个节点。对整个XML文档的交互(读取,写入,查找需要的元素),一般是在ElementTree层面进行的。对单个XML元素及其子元素,则是在Element层面进行的。下面我们举例介绍主要使用方法。

我们使用下面的XML文档,作为演示数据:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

<?xml version="1.0"?>

<doc>

  <branch name="codingpy.com" hash="1cdf045c">

    text,source

  </branch>

  <branch name="release01" hash="f200013e">

    <sub-branch name="subrelease01">

      xml,sgml

    </sub-branch>

  </branch>

  <branch name="invalid">

  </branch>

</doc>

接下来,我们加载这个文档,并进行解析:

?

1

2

>>> import xml.etree.ElementTree as ET

>>> tree = ET.ElementTree(file='doc1.xml')

然后,我们获取根元素(root element):

?

1

2

>>> tree.getroot()

<Element 'doc' at 0x11eb780>

正如之前所讲的,根元素(root)是一个Element对象。我们看看根元素都有哪些属性:

?

1

2

3

>>> root = tree.getroot()

>>> root.tag, root.attrib

('doc', {})

没错,根元素并没有属性。与其他Element对象一样,根元素也具备遍历其直接子元素的接口:

?

1

2

3

4

5

6

>>> for child_of_root in root:

...  print child_of_root.tag, child_of_root.attrib

...

branch {'hash': '1cdf045c', 'name': 'codingpy.com'}

branch {'hash': 'f200013e', 'name': 'release01'}

branch {'name': 'invalid'}

我们还可以通过索引值来访问特定的子元素:

?

1

2

>>> root[0].tag, root[0].text

('branch', '\n    text,source\n  ')

2、查找需要的元素

从上面的示例中,可以明显发现我们能够通过简单的递归方法(对每一个元素,递归式访问其所有子元素)获取树中的所有元素。但是,由于这是十分常见的工作,ET提供了一些简便的实现方法。

Element对象有一个iter方法,可以对某个元素对象之下所有的子元素进行深度优先遍历(DFS)。ElementTree对象同样也有这个方法。下面是查找XML文档中所有元素的最简单方法:

?

1

2

3

4

5

6

7

8

>>> for elem in tree.iter():

...  print elem.tag, elem.attrib

...

doc {}

branch {'hash': '1cdf045c', 'name': 'codingpy.com'}

branch {'hash': 'f200013e', 'name': 'release01'}

sub-branch {'name': 'subrelease01'}

branch {'name': 'invalid'}

在此基础上,我们可以对树进行任意遍历——遍历所有元素,查找出自己感兴趣的属性。但是ET可以让这个工作更加简便、快捷。iter方法可以接受tag名称,然后遍历所有具备所提供tag的元素:

?

1

2

3

4

5

6

>>> for elem in tree.iter(tag='branch'):

...  print elem.tag, elem.attrib

...

branch {'hash': '1cdf045c', 'name': 'codingpy.com'}

branch {'hash': 'f200013e', 'name': 'release01'}

branch {'name': 'invalid'}

3、支持通过XPath查找元素

使用XPath查找感兴趣的元素,更加方便。Element对象中有一些find方法可以接受Xpath路径作为参数,find方法会返回第一个匹配的子元素,findall以列表的形式返回所有匹配的子元素, iterfind则返回一个所有匹配元素的迭代器(iterator)。ElementTree对象也具备这些方法,相应地它的查找是从根节点开始的。

下面是一个使用XPath查找元素的示例:

?

1

2

3

4

>>> for elem in tree.iterfind('branch/sub-branch'):

...  print elem.tag, elem.attrib

...

sub-branch {'name': 'subrelease01'}

上面的代码返回了branch元素之下所有tag为sub-branch的元素。接下来查找所有具备某个name属性的branch元素:

?

1

2

3

4

>>> for elem in tree.iterfind('branch[@name="release01"]'):

...  print elem.tag, elem.attrib

...

branch {'hash': 'f200013e', 'name': 'release01'}

4、构建XML文档

利用ET,很容易就可以完成XML文档构建,并写入保存为文件。ElementTree对象的write方法就可以实现这个需求。

一般来说,有两种主要使用场景。一是你先读取一个XML文档,进行修改,然后再将修改写入文档,二是从头创建一个新XML文档。

修改文档的话,可以通过调整Element对象来实现。请看下面的例子:

?

1

2

3

4

5

6

7

8

>>> root = tree.getroot()

>>> del root[2]

>>> root[0].set('foo', 'bar')

>>> for subelem in root:

...  print subelem.tag, subelem.attrib

...

branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'codingpy.com'}

branch {'hash': 'f200013e', 'name': 'release01'}

在上面的代码中,我们删除了root元素的第三个子元素,为第一个子元素增加了新属性。这个树可以重新写入至文件中。最终的XML文档应该是下面这样的:

?

1

2

3

4

5

6

7

8

9

10

11

12

>>> import sys

>>> tree.write(sys.stdout)

<doc>

  <branch foo="bar" hash="1cdf045c" name="codingpy.com">

    text,source

  </branch>

  <branch hash="f200013e" name="release01">

    <sub-branch name="subrelease01">

      xml,sgml

    </sub-branch>

  </branch>

  </doc>

请注意,文档中元素的属性顺序与原文档不同。这是因为ET是以字典的形式保存属性的,而字典是一个无序的数据结构。当然,XML也不关注属性的顺序。

从头构建一个完整的文档也很容易。ET模块提供了一个SubElement工厂函数,让创建元素的过程变得很简单:

?

1

2

3

4

5

6

7

8

9

10

>>> a = ET.Element('elem')

>>> c = ET.SubElement(a, 'child1')

>>> c.text = "some text"

>>> d = ET.SubElement(a, 'child2')

>>> b = ET.Element('elem_b')

>>> root = ET.Element('root')

>>> root.extend((a, b))

>>> tree = ET.ElementTree(root)

>>> tree.write(sys.stdout)

<root><elem><child1>some text</child1><child2 /></elem><elem_b /></root>

5、利用iterparse解析XML流

XML文档通常都会比较大,如何直接将文档读入内存的话,那么进行解析时就会出现问题。这也就是为什么不建议使用DOM,而是SAX API的理由之一。

我们上面谈到,ET可以将XML文档加载为保存在内存里的树(in-memory tree),然后再进行处理。但是在解析大文件时,这应该也会出现和DOM一样的内存消耗大的问题吧?没错,的确有这个问题。为了解决这个问题,ET提供了一个类似SAX的特殊工具——iterparse,可以循序地解析XML。

接下来,笔者为大家展示如何使用iterparse,并与标准的树解析方式进行对比。我们使用一个自动生成的XML文档,下面是该文档的开头部分:

?

1

2

3

4

5

6

7

8

9

10

11

12

<?xml version="1.0" standalone="yes"?>

<site>

 <regions>

  <africa>

   <item id="item0">

    <location>United States</location<!-- Counting locations -->

    <quantity>1</quantity>

    <name>duteous nine eighteen </name>

    <payment>Creditcard</payment>

    <description>

     <parlist>

[...]

我们来统计一下文档中出现了多少个文本值为Zimbabwe的location元素。下面是使用ET.parse的标准方法:

?

1

2

3

4

5

6

7

8

tree = ET.parse(sys.argv[2])

 

count = 0

for elem in tree.iter(tag='location'):

  if elem.text == 'Zimbabwe':

    count += 1

 

print count

上面的代码会将全部元素载入内存,逐一解析。当解析一个约100MB的XML文档时,运行上面脚本的Python进程的内存使用峰值为约560MB,总运行时间问2.9秒。

请注意,我们其实不需要讲整个树加载到内存里。只要检测出文本为相应值得location元素即可。其他数据都可以废弃。这时,我们就可以用上iterparse方法了:

?

1

2

3

4

5

6

7

8

count = 0

for event, elem in ET.iterparse(sys.argv[2]):

  if event == 'end':

    if elem.tag == 'location' and elem.text == 'Zimbabwe':

      count += 1

  elem.clear() # 将元素废弃

 

print count

上面的for循环会遍历iterparse事件,首先检查事件是否为end,然后判断元素的tag是否为location,以及其文本值是否符合目标值。另外,调用elem.clear()非常关键:因为iterparse仍然会生成一个树,只是循序生成的而已。废弃掉不需要的元素,就相当于废弃了整个树,释放出系统分配的内存。

当利用上面这个脚本解析同一个文件时,内存使用峰值只有7MB,运行时间为2.5秒。速度提升的原因,是我们这里只在树被构建时,遍历一次。而使用parse的标准方法是先完成整个树的构建后,才再次遍历查找所需要的元素。

iterparse的性能与SAX相当,但是其API却更加有用:iterparse会循序地构建树;而利用SAX时,你还得自己完成树的构建工作。

以上就是为大家分享的Python解析XML的几种方式,希望对大家的学习有所帮助。

 

转自:https://www.jb51.net/article/79494.htm

<think>我们需要分析Git推送失败的原因:错误信息是"error Missing tree e7e892b7768b211d5238e0d770d9492f2e998de6",这表明远程仓库缺少一个tree对象(哈希值为e7e892b...)。此外,远程解包失败(remote unpack failed)。 可能的原因及解决方案: 1. **本地仓库与远程仓库不同步**: 远程仓库可能已经更新,而本地仓库落后。需要先拉取远程分支的变更,解决可能的冲突,再重新推送。 2. **缺少的tree对象可能是由于历史重写导致**: 如果本地执行了变基(rebase)、修改提交(amend)或过滤分支(filter-branch)等操作,可能会改变提交历史,使得一些对象在远程不存在。 3. **Gerrit服务器的存储问题**: 服务器端可能确实缺少该tree对象,可能是存储损坏或清理操作导致。 4. **网络问题或权限问题**(虽然错误信息不是典型的权限问题)。 解决步骤: 步骤1:确保本地分支基于最新的远程分支 git fetch origin git rebase origin/b/ossi/release-16.0.0 # 或者使用git merge 步骤2:如果步骤1不能解决,检查该tree对象是否在本地存在 git cat-file -t e7e892b7768b211d5238e0d770d9492f2e998de6 如果存在,会返回"tree";如果不存在,则说明本地仓库也缺失,需要从其他地方恢复。 步骤3:如果本地有该tree对象,但远程缺失,可能是服务器问题。联系服务器管理员检查Gerrit服务器的存储。 步骤4:如果这个错误发生在多人协作的项目中,可能是其他人在推送时出现了问题,导致服务器状态异常。 步骤5:如果以上都不行,尝试重新克隆仓库,然后重新应用本地修改(注意保存工作),再推送。 注意:由于错误信息显示是tree对象缺失,而不是commit对象,因此很可能是历史重写导致,或者是服务器端存储问题。 根据错误信息,推送的引用是`HEAD:refs/for/b/ossi/release-16.0.0`,这是推送到Gerrit进行代码审查的标准方式。Gerrit要求每个提交都是独立的,所以如果提交历史中有任何问题,可能会导致此类错误。 另外,观察输出中的“pack-reused 0”和“remote: Counting objects: 1641151, done”,说明远程仓库在接收推送时尝试解包但失败。 因此,最可能的解决方法是重新基于远程分支变基,确保本地提交历史与远程一致。 操作示例: ```bash # 拉取远程的最新代码 git fetch origin # 切换到你的分支(确保你已经在正确的分支上) git checkout your_branch # 变基到远程分支(这里假设远程分支名为b/ossi/release-16.0.0) git rebase origin/b/ossi/release-16.0.0 # 解决可能的冲突,然后再次推送 git push origin HEAD:refs/for/b/ossi/release-16.0.0 ``` 如果问题依旧,可能需要检查本地仓库的完整性(使用`git fsck`)或者联系Gerrit服务器管理员。 </think>### Git 推送失败原因分析与解决方案 #### 错误核心原因:**Missing tree object** ``` error: remote unpack failed: error Missing tree e7e892b7768b211d5238e0d770d9492f2e998de6 ``` 表明 Gerrit 服务器在解包时无法找到指定的树对象(tree object),该对象是 Git 仓库数据结构的基础组成部分。 --- ### 根本原因分析 1. **本地仓库损坏** - 可能由于不完整克隆或磁盘错误导致本地缺少关键对象 - 验证命令:`git fsck --full` 2. **历史改写不一致** - 在 `rebase`/`amend` 后未及时同步上游变更 - 特别常见于多人协作的分支开发场景 3. **Gerrit 引用存储异常** - 服务器端 `refs/changes/` 引用丢失或损坏 - 需 Gerrit 管理员检查仓库完整性 4. **部分提交对象缺失** - 某些提交依赖的树对象未完整推送 - 常发生于大文件或复杂合并提交 --- ### 解决方案指南 #### 第一步:修复本地仓库 ```bash # 清理无效对象 git prune git gc --aggressive # 验证仓库完整性 git fsck --full ``` #### 第二步:重建引用关系 ```bash # 强制更新所有远程引用 git fetch --all --prune --tags --force # 基于最新远程分支重建 git rebase origin/b/ossi/release-16.0.0 ``` #### 第三步:分块推送 ```bash # 将大提交拆分为小块推送 git push origin HEAD~3:refs/for/b/ossi/release-16.0.0 # 先推送前3个提交 git push origin HEAD:refs/for/b/ossi/release-16.0.0 # 再推送剩余提交 ``` #### 第四步:服务器端修复(需管理员) 1. 在 Gerrit 服务器执行: ```bash gerrit flush-caches --all java -jar gerrit.war reindex -d /path/to/site ``` 2. 检查存储后端(如 GitBlit)日志中的对象校验错误 --- ### 预防措施 1. **定期同步策略** ```bash # 每日工作前执行 git pull --rebase origin b/ossi/release-16.0.0 ``` 2. **提交粒度控制** - 单次提交变更文件 ≤ 20 个 - 避免 >5MB 的二进制文件提交 3. **启用提交签名** 在 `git config` 中添加: ```ini [commit] gpgsign = true ``` --- ###
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值