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. Если важна скорость доступа по атрибутам, заранее «прогрейте» объектные элементы одноразовым проходом, используйте их на всех этапах обработки и удалите кэш, когда ускорение перестанет быть нужным. Это прямолинейный способ обменять память на стабильную скорость доступа.