1. 程式人生 > >python之lxml快速上手_Element(一)

python之lxml快速上手_Element(一)

The Element class

對於使用任何一種程式語言的開發人員來說,xml的處理總是不可避免,甚至是非常常見的。而lxml則是在python語言中,功能最豐富、最易於使用,同時效能也相當不錯的xml、html處理庫。雖然網上也有許多介紹lxml用法的文章,但是,學習任意一個第三方庫(框架、新技術),官方文件無疑是不可多得的第一手好材料。於是,為了讓其他有需要的同學也能快速上手,針對手冊中The lxml.etree Tutorial部分,進行了部分翻譯。同時,本人英語水平有限,或者理解上有偏差,望不吝指正。

ElementElementTree API中是主要的容器物件。大部分XML tree

函式都是通過它訪問的。要建立它也是很簡單,只需使用Element工廠函式:

>>> root = etree.Element("root")

xml節點的標籤名可通過tag屬性訪問:

>>> print(root.tag)
root

Elements在xml中通過樹狀結構組織的。如果需要建立子節點,並把它們追加到一個父節點,你可以使用append()方法:

>>> root.append( etree.Element("child1") )

然而,(建立子節點)的需求是如此普遍,有一個更加簡短、高效的方式:使用SubElement

工廠函式。跟Element一樣,它接受同樣的引數,但是需額外的一個父節點引數作為它的第一個引數:

>>> child2 = etree.SubElement(root, "child2")
>>> child3 = etree.SubElement(root, "child3")

為了更加直觀地瞭解到剛才建立的就是xml,你可以使用序列化:

>>> print(etree.tostring(root, pretty_print=True))
<root>
  <child1/>
  <child2/>
<child3/> </root>

Elements are lists

為了更加簡單、直接地訪問(上面所建立的)子節點,elements儘可能地模仿常規python List的行為:

>>> child = root[0]
>>> print(child.tag)
child1

>>> print(len(root))
3

>>> root.index(root[1]) # lxml.etree only!
1

>>> children = list(root)

>>> for child in root:
...     print(child.tag)
child1
child2
child3

>>> root.insert(0, etree.Element("child0"))
>>> start = root[:1]
>>> end   = root[-1:]

>>> print(start[0].tag)
child0
>>> print(end[0].tag)
child3

ElementTree 1.3 and lxml 2.0之前,你可以檢查一個Element的真假值,來判定它是否有孩子節點。如果孩子節點組成的list是空的:

if root:   # 在後續版本中將不再起作用
    print("The root element has children")

上面的條件測試將不再起作用。因此,許多使用者可能會驚奇地發現,任意一個節點像上面一樣進行條件測試,結果都是False。作為替代的做法,使用len(element)語意上更加明確, 也意味著更少的錯誤傾向。

>>> print(etree.iselement(root))  # 測試root是否是某種型別的Element
True
>>> if len(root):                 # 測試root是否有孩子節點
...     print("The root element has children")
The root element has children

還有一個需要說明的情況,lxml中Elements的行為 (在2.0及之後的版本中)與常規python List、原始的ElementTree會有偏差。

>>> for child in root:
...     print(child.tag)
child0
child1
child2
child3
>>> root[0] = root[-1]  # this moves the element in lxml.etree!
>>> for child in root:
...     print(child.tag)
child3
child1
child2

在上面的例子中,最後一個節點被移動到不同的位置(第一個),而不是被拷貝到另一個位置。當它被移至另一個不同的位置,它從它原有的位置被移除。在一般的list中,物件可以在同一時間點出現在不同位置,而像上面的情況,在list中僅會拷貝最後一個節點的引用到第一個位置,因此list中包含兩個同樣的物件:

>>> l = [0, 1, 2, 3]
>>> l[0] = l[-1]
>>> l
[3, 1, 2, 3]

注意,在原始的ElementTree中,一個Element物件可以位於任意xml樹物件的任意一個位置,它允許像list一樣執行同樣的拷貝操作。很明顯的一個缺點,對節點的任意改變都將應用到所有它在tree中出現的地方。而這卻不一定是你想要的。
上面這種特殊的處理方式,有一個好處就是:在lxml.etree中的任意一個Element都有唯一一個父節點,可通過getparent()獲取,而在原始ElementTree中是不支援的。

>>> root is root[0].getparent()  # lxml.etree only!
True

如果你想在lxml.etree中拷貝一個節點到另外一個位置,可以考慮使用獨立的標準庫模組copydeepcopy()

>>> from copy import deepcopy

>>> element = etree.Element("neu")
>>> element.append( deepcopy(root[1]) )

>>> print(element[0].tag)
child1
>>> print([ c.tag for c in root ])
['child3', 'child1', 'child2']

如果你想訪問一個節點的兄弟節點,可以:

>>> root[0] is root[1].getprevious() # lxml.etree only!
True
>>> root[1] is root[0].getnext() # lxml.etree only!
True

Elements carry attributes as a dict

XML節點支援屬性,你可以直接在Element工廠函式中構建它們:

>>> root = etree.Element("root", interesting="totally")
>>> etree.tostring(root)
b'<root interesting="totally"/>'

屬性僅僅是無序的鍵值(key-value)對,因此,處理它們的一個簡便方式就是通過Elements的類字典介面:

>>> print(root.get("interesting"))
totally

>>> print(root.get("hello"))
None
>>> root.set("hello", "Huhu")
>>> print(root.get("hello"))
Huhu

>>> etree.tostring(root)
b'<root interesting="totally" hello="Huhu"/>'

>>> sorted(root.keys())
['hello', 'interesting']

>>> for name, value in sorted(root.items()):
...     print('%s = %r' % (name, value))
hello = 'Huhu'
interesting = 'totally'

有時,你只是想做item的查詢,或者因為一些其他的原因,想要獲得“真實”的類字典物件,並把它傳遞給周邊,你可以使用attrib屬性:

>>> attributes = root.attrib

>>> print(attributes["interesting"])
totally
>>> print(attributes.get("no-such-attribute"))
None

>>> attributes["hello"] = "Guten Tag"
>>> print(attributes["hello"])
Guten Tag
>>> print(root.get("hello"))
Guten Tag

注意,attrib是一個由Element本身支援的類字典物件。這也意味者對Element所做的改動會反射到attrib上,同樣,XML tree將一直保持“啟用”狀態,只要它的任意節點的attrib還在使用中。為了獲取一個獨立的,不依賴於XML tree的attrib快照,可以把它拷貝到一個字典:

>>> d = dict(root.attrib)
>>> sorted(d.items())
[('hello', 'Guten Tag'), ('interesting', 'totally')]

Elements contain text

Elements可以包含文字:

>>> root = etree.Element("root")
>>> root.text = "TEXT"

>>> print(root.text)
TEXT

>>> etree.tostring(root)
b'<root>TEXT</root>'

對於許多xml文件(以資料為主),這是唯一能找到文字的地方。(文字)通常被tree底部的葉子節點包裹者。
然而,如果xml被用作標籤文字例如HTML,文字也可以出現在不同的節點之間:

<html><body>Hello<br/>World</body></html>

在這裡,
標籤被文字包圍。這在document-stylemixed-content型別的xml中經常被提及。 Elements使用通過tail屬性來支援這一點的。它包含緊跟該節點的文字,直到XML tree中的下一個節點:

>>> html = etree.Element("html")
>>> body = etree.SubElement(html, "body")
>>> body.text = "TEXT"

>>> etree.tostring(html)
b'<html><body>TEXT</body></html>'

>>> br = etree.SubElement(body, "br")
>>> etree.tostring(html)
b'<html><body>TEXT<br/></body></html>'

>>> br.tail = "TAIL"
>>> etree.tostring(html)
b'<html><body>TEXT<br/>TAIL</body></html>'

這兩個屬性.text.tail已經足夠呈現XML文件中的任意文字。
然而,當你序列化一個Element時,你並總是希望tail text出現在結果中。為了達成這個目的,tostring()函式接受一個關鍵字引數with_tail:

>>> etree.tostring(br)
b'<br/>TAIL'
>>> etree.tostring(br, with_tail=False) # lxml.etree only!
b'<br/>'

如果你僅僅需要文字text,不需要中間的節點,你必須以正確的順序,遞迴連線所有的texttail text屬性。再一次,tostring()方法大顯身手,這一次使用method關鍵字引數:

>>> etree.tostring(html, method="text")
b'TEXTTAIL'