2026, Jan 13 15:01
Кэширование в lxml.objectify: ускоряем доступ к атрибутам XML
Разбираем приём кэширования в lxml.objectify: статическая инстанциация Python-объектов для XML позволяет ускорить доступ по атрибутам. Пример кода и пояснения.
Ускорить доступ к атрибутам в lxml.objectify можно за счёт кэширования объектных элементов — это практичный приём, в котором память обменивается на более быстрые обращения. Подход описан в заметках о производительности lxml и сводится к статической инстанциации Python-объектов для всего XML-дерева, чтобы при повторных обращениях переиспользовать уже созданные объекты, а не создавать их на лету.
Пример кода: воспроизводим исходную ситуацию
Чтобы показать исходную точку, возьмём такой тестовый XML:
<Forms>
<greeting>Hello, world!</greeting>
<Form_1>
<Country>AFG</Country>
<Country>AFG</Country>
<Country>IND</Country>
</Form_1>
<Form_1>
<Country>IND</Country>
<Country>USA</Country>
</Form_1>
</Forms>
Следующий фрагмент выводит идентификатор объекта, полученного через доступ по атрибуту, до построения какого‑либо кэша:
from lxml import etree, objectify
def show_identity_before_cache(xml_file):
doc_obj = objectify.parse(xml_file)
top_node = doc_obj.getroot()
# Идентификатор и значение элемента, полученного через доступ по атрибуту
print(id(top_node.Form_1.Country), top_node.Form_1.Country)
Что происходит на самом деле
Оптимизация опирается на простой кэш: вы один раз проходите дерево, принудительно создаёте объекты всех элементов и сохраняете результат. После этого одинаковые элементы, полученные разными способами доступа, указывают на один и тот же адрес в памяти. На практике это означает ускорение обычного доступа по атрибутам за счёт дополнительного расхода памяти.
Один из способов ускорить обычный доступ к атрибутам — статическая инстанциация Python‑объектов, то есть обмен памяти на скорость
Решение и рабочий код
Функция ниже показывает, как построить кэш, проверить тождественность объектов и при необходимости освободить закэшированные элементы.
from lxml import etree, objectify
def apply_cache_and_compare(xml_file):
doc_obj = objectify.parse(xml_file)
top_node = doc_obj.getroot()
# До кэширования: идентификатор элемента при доступе по атрибуту
print(id(top_node.Form_1.Country), top_node.Form_1.Country)
# Построение кэша: статическая инстанциация всех элементов документа
memo_box = {}
memo_box[top_node] = list(doc_obj.iter())
# После кэширования: идентификатор через кэш и через доступ по атрибуту
print(id(memo_box[top_node][3]), memo_box[top_node][3])
print(id(top_node.Form_1.Country), top_node.Form_1.Country)
# Обе ссылки указывают на один и тот же объект в памяти
print(top_node.Form_1.Country is memo_box[top_node][3])
# Объект, полученный по XPath, также сводится к тому же закэшированному объекту
picked = top_node.xpath('(//Form_1/Country)[1]')[0]
print(picked is memo_box[top_node][3])
# При желании удалите запись кэша, когда ускорение больше не нужно
del memo_box[top_node]
Это демонстрирует, что после заполнения кэша элементы, полученные через доступ по атрибуту и через XPath, имеют одинаковую идентичность объектов, что подтверждает: всё дерево документа создано заранее и переиспользуется.
Почему это важно
Когда вы многократно обходите или разыменовываете множество элементов, важно избежать лишних созданий объектов. Однократно закэшировав полный список элементов, вы снижаете накладные расходы при последующих обращениях по атрибутам — ценой хранения этих Python‑объектов в памяти, пока живёт запись кэша.
Практические выводы
Применяйте кэширование, когда планируете часто обращаться к множеству элементов в «горячих» местах и готовы к обмену памяти на скорость. Постройте кэш сразу после разбора с помощью cache[root] = list(root.iter()), используйте закэшированные объекты в процессе обработки и удаляйте запись командой del cache[root], когда задача завершена. Упомянутые в заметках о производительности lxml методы также доступны в репозитории lxml в файле benchmark/bench_objectify.py для дальнейшего изучения.
Итог
Кэширование со статической инстанциацией — простой и результативный приём в lxml.objectify. Если важна скорость доступа по атрибутам, заранее «прогрейте» объектные элементы одноразовым проходом, используйте их на всех этапах обработки и удалите кэш, когда ускорение перестанет быть нужным. Это прямолинейный способ обменять память на стабильную скорость доступа.