Хотя ответ Луи верен, и я полностью согласен с тем, что изменение структуры данных при ее обходе обычно является плохой идеей (tm) , вы также спросили, почему код работает с xml.etree.ElementTree
, а не lxml.etree
и этому есть очень разумное объяснение.
Реализация .append
в xml.etree.ElementTree
Эта библиотека реализована непосредственно в Python и может варьироваться в зависимости от того, в какой среде Python вы работаетес помощью.Предполагая, что вы используете CPython, реализация, которую вы ищете, реализована в vanilla Python :
def append(self, subelement):
"""Add *subelement* to the end of this element.
The new element will appear in document order after the last existing
subelement (or directly after the text, if it's the first subelement),
but before the end tag for this element.
"""
self._assert_is_element(subelement)
self._children.append(subelement)
Последняя строка - единственная часть, которая нас интересует.Оказывается, self._children
инициализируется в верхней части этого файла как:
self._children = []
Таким образом, добавление дочернего элемента в дерево просто добавляет элемент в список.Интуитивно, это именно то, что вы ищете (в данном случае), и реализация ведет себя совершенно неудивительно.
Реализация .append
в lxml.etree
lxml
реализованакак смесь Python, нетривиального кода Cython и кода C, настолько тщательно проработать его было значительно сложнее, чем в чисто Python-реализации.Во-первых, .append
реализовано как :
def append(self, _Element element not None):
u"""append(self, element)
Adds a subelement to the end of this element.
"""
_assertValidNode(self)
_assertValidNode(element)
_appendChild(self, element)
_appendChild
реализовано в apihelper.pxi
:
cdef int _appendChild(_Element parent, _Element child) except -1:
u"""Append a new child to a parent element.
"""
c_node = child._c_node
c_source_doc = c_node.doc
# prevent cycles
if _isAncestorOrSame(c_node, parent._c_node):
raise ValueError("cannot append parent to itself")
# store possible text node
c_next = c_node.next
# move node itself
tree.xmlUnlinkNode(c_node)
tree.xmlAddChild(parent._c_node, c_node)
_moveTail(c_next, c_node)
# uh oh, elements may be pointing to different doc when
# parent element has moved; change them too..
moveNodeToDocument(parent._doc, c_source_doc, c_node)
return 0
Здесь определенно происходит немного больше.В частности, lxml
явно удаляет узел из дерева, а затем добавляет его в другом месте.Это предотвращает случайное создание циклического XML графа при манипулировании узлами (что вы, вероятно, можете сделать с xml.etree
версией).
Методы обхода для lxml
Теперь, когда мы знаем, что xml.etree
копирует узлы при добавлении, но lxml.etree
перемещает их, почему эти обходные пути работают?Основанный на методе tree.xmlUnlinkNode
(который на самом деле определен в C внутри libxml2
), удаление связей просто мешает кучу указателей.Таким образом, все, что копирует метаданные узла, сделает свое дело.Поскольку все метаданные, которые нас интересуют, являются прямыми полями в xmlNode
struct , все, что мелкие копирует узлы, справится с задачей
copy.deepcopy()
определенно работает node.xpath
возвращает узлы , завернутые в прокси-элементы , что происходит с копией метаданных дерева copy.copy()
также выполняеттрюк - Если вам не нужно, чтобы ваши комбинации находились в официальном дереве, настройка
new_combo_tree = []
также дает вам добавление в список, как xml.etree
.
Если вы 'Если вы действительно обеспокоены производительностью и большими деревьями, я бы, вероятно, начал с мелкого копирования с copy.copy()
, хотя вы должны обязательно профилировать несколько различных вариантов и посмотреть, какой из них лучше всего подходит для вас.