Компиляция re.compile и производительность

Компилируем паттерн один раз и переиспользуем — это удобнее и иногда быстрее.

re.compile(pattern) превращает строку-паттерн в объект-шаблон с методами search, match, findall, sub.

Идея компиляции

Каждый вызов re.search(pattern, text) разбирает строку-паттерн заново (хотя у модуля есть внутренний кэш). Если один и тот же паттерн применяется много раз — например, в цикле по тысячам строк, — его удобно скомпилировать один раз в объект и дальше звать методы у него:

import re

number = re.compile(r"\d+")

print(number.findall("a1 b2 c3"))
print(number.search("цена 42 руб").group())
print(number.sub("#", "1 и 2 и 3"))

Вывод:

['1', '2', '3']
42
# и # и #

У скомпилированного объекта те же методы (findall, search, sub...), только паттерн уже «вшит». Это устраняет повтор паттерна в каждом вызове.

Главная польза — читаемость

Компиляция придаёт паттерну имя. number.findall(text) читается лучше, чем безымянный \d+ в десятке мест. Паттерны собирают в одном месте (например, в начале модуля), документируют и переиспользуют — код становится опрятнее.

Про производительность

Модуль re кэширует последние использованные паттерны, поэтому для пары вызовов разница незаметна. Но в горячих циклах явная компиляция убирает накладные расходы на поиск в кэше и слегка ускоряет работу. Главное — не компилировать один и тот же паттерн заново внутри цикла:

import re

lines = ["id=10", "id=25", "нет числа", "id=7"]
rx = re.compile(r"id=(\d+)")   # компилируем ОДИН раз

result = []
for line in lines:
    m = rx.search(line)
    if m:
        result.append(int(m.group(1)))
print(result)

Вывод:

[10, 25, 7]

Паттерн id=(\d+) скомпилирован до цикла, а в теле цикла только применяется. Это и быстрее, и нагляднее.

Флаги при компиляции

Флаги передают вторым аргументом compile: re.compile(r"cat", re.IGNORECASE). После этого регистр учитываться не будет во всех вызовах объекта.

Итог

  • re.compile возвращает объект-шаблон с методами search, findall, sub и др.
  • Главная польза — читаемость и переиспользование именованного паттерна.
  • В горячих циклах компилируйте паттерн ОДИН раз снаружи, а внутри только применяйте.
  • Флаги задают вторым аргументом: re.compile(pattern, re.IGNORECASE).
Проверьте себя
1. Что возвращает re.compile(r"\d+")?
AСписок совпадений
BОбъект-шаблон с методами search, findall, sub и т.д.
CСтроку
DПервое совпадение
2. Как правильно использовать компиляцию в цикле по многим строкам?
AКомпилировать паттерн заново на каждой итерации
BСкомпилировать паттерн один раз ДО цикла и применять его внутри
CКомпиляция в циклах запрещена
DНе использовать цикл вообще
3. В чём основная практическая польза re.compile, помимо скорости?
AПаттерн становится регистронезависимым автоматически
BПаттерн получает имя и переиспользуется — код читаемее
CМожно использовать сторонние библиотеки
DСовпадения находятся точнее
Поддержать проект