python之lxml快速上手_Element(一)
The Element class
對於使用任何一種程式語言的開發人員來說,xml的處理總是不可避免,甚至是非常常見的。而lxml則是在python語言中,功能最豐富、最易於使用,同時效能也相當不錯的xml、html處理庫。雖然網上也有許多介紹lxml用法的文章,但是,學習任意一個第三方庫(框架、新技術),官方文件無疑是不可多得的第一手好材料。於是,為了讓其他有需要的同學也能快速上手,針對手冊中The lxml.etree Tutorial部分,進行了部分翻譯。同時,本人英語水平有限,或者理解上有偏差,望不吝指正。
Element在ElementTree API中是主要的容器物件。大部分XML tree
>>> root = etree.Element("root")
xml節點的標籤名可通過tag屬性訪問:
>>> print(root.tag)
root
Elements在xml中通過樹狀結構組織的。如果需要建立子節點,並把它們追加到一個父節點,你可以使用append()方法:
>>> root.append( etree.Element("child1") )
然而,(建立子節點)的需求是如此普遍,有一個更加簡短、高效的方式:使用SubElement
>>> 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中拷貝一個節點到另外一個位置,可以考慮使用獨立的標準庫模組copy的deepcopy():
>>> 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-style、mixed-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,不需要中間的節點,你必須以正確的順序,遞迴連線所有的text、tail text屬性。再一次,tostring()方法大顯身手,這一次使用method關鍵字引數:
>>> etree.tostring(html, method="text")
b'TEXTTAIL'