Компиляция регулярных выражений в Python

Я работаю через Doug Hellman "Стандартная библиотека Python по примеру" и наткнулся на это:

"1.3.2 Компиляция выражений re включает функции уровня модуля для работы с регулярными выражениями в виде текстовых строк, но более эффективно компилировать выражения, используемые программой часто. "

Я не мог следовать его объяснениям, почему это так. Он говорит, что "функции уровня модуля поддерживают кэш скомпилированных выражений" и что, поскольку "размер кеша" ограничен, "использование скомпилированных выражений напрямую устраняет служебные данные поиска кэша".

Я был бы очень признателен, если бы кто-нибудь мог объяснить или направить меня к объяснению, которое я мог бы лучше понять, почему более эффективно компилировать регулярные выражения, используемые программой часто, и как этот процесс действительно работает.

Ответ 1

Hm. Это странно. Мои знания до сих пор (полученные из другого источника из этого вопроса) предложили мой первоначальный ответ:


Первый ответ

Python кэширует последние 100 регулярных выражений, которые вы использовали, поэтому даже если вы не компилируете их явно, их не нужно перекомпилировать при каждом использовании.

Однако есть два недостатка: когда достигнут предел в 100 регулярных выражений, весь кеш отключен, поэтому, если вы используете 101 различные регулярные выражения в строке, каждый из них будет перекомпилирован каждый раз. Ну, это маловероятно, но все же.

Во-вторых, чтобы узнать, было ли уже скомпилировано регулярное выражение, интерпретатор должен искать регулярное выражение в кеше каждый раз, когда требуется немного дополнительного времени (но не так много, поскольку словарные запросы очень быстрые).

Итак, если вы явно компилируете свои регулярные выражения, вы избегаете этого дополнительного шага поиска.


Update

Я только что проверил (Python 3.3):

>>> import timeit
>>> timeit.timeit(setup="import re", stmt='''r=re.compile(r"\w+")\nfor i in range(10):\n r.search("  jkdhf  ")''')
18.547793477671938
>>> timeit.timeit(setup="import re", stmt='''for i in range(10):\n re.search(r"\w+","  jkdhf  ")''')
106.47892003890324

Итак, похоже, что кеширование не выполняется. Возможно, что выполняется причуда особых условий, при которых timeit.timeit() работает?

С другой стороны, в Python 2.7 разница не так заметна:

>>> import timeit
>>> timeit.timeit(setup="import re", stmt='''r=re.compile(r"\w+")\nfor i in range(10):\n r.search("  jkdhf  ")''')
7.248294908492429
>>> timeit.timeit(setup="import re", stmt='''for i in range(10):\n re.search(r"\w+","  jkdhf  ")''')
18.26713670282241

Ответ 2

Я верю, что он пытается сказать, что вы не должны компилировать свое регулярное выражение внутри цикла, а вне его. Затем вы можете просто запустить уже скомпилированный код внутри цикла.

вместо:

while true: 
    result = re.match('A', str)

Вы должны поставить:

regex = re.compile('A')
while true:
    result = regex.match(str)

В основном re.match(pattern, str) объединяет этап компиляции и сопоставления. Компиляция одного и того же шаблона внутри цикла неэффективна и поэтому должна быть поднята вне цикла.

См. ответ Тима для правильных рассуждений.

Ответ 3

Мне кажется, что автор просто говорит, что более эффективно компилировать регулярное выражение и сохранять его, чем рассчитывать на ранее скомпилированную версию, которая все еще хранится в внутреннем кеше ограниченного размера модуля. Вероятно, это связано с тем, сколько усилий требуется для их компиляции, а также лишние служебные данные дополнительного кэша, которые в первую очередь должны быть больше, чем клиент, просто сохраняя их сами.