Кроме того факта, что в 2.6 re.subне будет аргумента флагов ...
new123456
58
Я только что натолкнулся на случай, когда использование re.compileдало улучшение в 10-50 раз. Мораль такова: если у вас есть много регулярных выражений (больше, чем MAXCACHE = 100), и вы используете их много раз каждый (и разделены более чем двумя регулярными выражениями MAXCACHE между ними, так что каждый из них сбрасывается из кэша: используйте то же самое много раз, а затем переход к следующему не считается), тогда это определенно поможет скомпилировать их. В противном случае это не имеет значения.
ShreevatsaR
8
Следует отметить одну небольшую вещь: для строк, которые не нуждаются в регулярном выражении, inпроверка подстрок строки выполняется НАМНОГО быстрее:>python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 1.41 usec per loop>python -m timeit "x = 'hello' in 'hello world'" 10000000 loops, best of 3: 0.0513 usec per loop
Gamrix
@ShreevatsaR Интересно! Можете ли вы опубликовать ответ с примером, показывающим улучшение в 10–50 раз? Большинство ответов, приведенных здесь, фактически показывают 3-кратное улучшение в некоторых точных случаях, а в других случаях улучшение практически отсутствует.
Басж
1
@Basj Готово, опубликовал ответ . Я не стал копать то, для чего использовал Python в декабре 2013 года, но первая простая попытка, которую я попробовал, показывает то же поведение.
ShreevatsaR
Ответы:
436
У меня был большой опыт работы с скомпилированным регулярным выражением 1000 раз по сравнению с компиляцией на лету, и я не заметил какой-либо ощутимой разницы. Очевидно, что это анекдотично, и, конечно, это не лучший аргумент против компиляции, но я обнаружил, что разница незначительна.
РЕДАКТИРОВАТЬ: После быстрого взгляда на фактический код библиотеки Python 2.5, я вижу, что Python внутренне компилирует и кэширует регулярные выражения всякий раз, когда вы используете их в любом случае (включая вызовы re.match()), так что вы действительно изменяете только КОГДА регулярное выражение компилируется, и не должно ' не экономить много времени - только время, необходимое для проверки кэша (поиск ключа по внутреннему dictтипу).
Из модуля re.py (комментарии мои):
def match(pattern, string, flags=0):return _compile(pattern, flags).match(string)def _compile(*key):# Does cache check at top of function
cachekey =(type(key[0]),)+ key
p = _cache.get(cachekey)if p isnotNone:return p
# ...# Does actual compilation on cache miss# ...# Caches compiled regexif len(_cache)>= _MAXCACHE:
_cache.clear()
_cache[cachekey]= p
return p
Я до сих пор часто предварительно компилирую регулярные выражения, но только для того, чтобы связать их с красивым, многократно используемым именем, а не для ожидаемого увеличения производительности.
Ваш вывод не соответствует вашему ответу. Если регулярные выражения компилируются и сохраняются автоматически, в большинстве случаев нет необходимости делать это вручную.
JFS
84
Дж. Ф. Себастьян, это служит сигналом для программиста, что рассматриваемое регулярное выражение будет много использоваться и не должно быть отбрасыванием.
калейссин
40
Более того, я бы сказал, что если вы не хотите подвергаться ударам компиляции и кэширования в какой-то критически важной для вашего приложения части, лучше всего скомпилировать их перед установкой некритической части вашего приложения. ,
Эдди Паркер
20
Я вижу главное преимущество использования скомпилированного регулярного выражения, если вы повторно используете одно и то же регулярное выражение несколько раз, тем самым уменьшая вероятность опечаток. Если вы просто вызываете его один раз, то некомпилированный будет более читабельным.
Monkut
18
Итак, основное отличие будет в том, что когда вы используете много различных регулярных выражений (больше, чем _MAXCACHE), некоторые из них только один раз, а другие много раз ... тогда важно сохранить ваши скомпилированные выражения для тех, которые используются чаще, чтобы они не выгружаются из кеша, когда он полон.
Фортран
133
Для меня самое большое преимущество re.compile является возможность отделить определение регулярного выражения от его использования.
Даже простое выражение, такое как 0|[1-9][0-9]*(целое число в основании 10 без начальных нулей), может быть достаточно сложным, чтобы вам не пришлось его перепечатывать, проверять, не были ли сделаны опечатки, а позже придется перепроверять наличие опечаток при запуске отладки. , Кроме того, лучше использовать имя переменной, например, num или num_b10, чем 0|[1-9][0-9]*.
Конечно, можно хранить строки и передавать их в re.match; однако, это менее читабельно:
num ="..."# then, much later:
m = re.match(num, input)
По сравнению с компиляцией:
num = re.compile("...")# then, much later:
m = num.match(input)
Хотя это довольно близко, последняя строка второй кажется более естественной и простой при повторном использовании.
Я согласен с этим ответом; часто использование re.compile приводит к большему, не менее читабельному коду.
Карл Мейер
1
Иногда, наоборот, верно - например, если вы определяете регулярное выражение в одном месте и используете соответствующие группы в другом отдаленном месте.
Кен Уильямс
1
@KenWilliams Не обязательно, хорошо названное регулярное выражение для конкретной цели должно быть ясным, даже если оно используется далеко от исходного определения. Например, us_phone_numberи social_security_numberт. Д.
Брайан М. Шелдон
2
@ BrianM. Шелдон, хорошо называя регулярное выражение, на самом деле не поможет вам понять, что представляют его различные группы захвата.
Кен Уильямс
69
FWIW:
$ python -m timeit -s "import re""re.match('hello', 'hello world')"100000 loops, best of 3:3.82 usec per loop
$ python -m timeit -s "import re; h=re.compile('hello')""h.match('hello world')"1000000 loops, best of 3:1.26 usec per loop
поэтому, если вы собираетесь часто использовать одно и то же регулярное выражение, это может стоить того re.compile(особенно для более сложных регулярных выражений).
Применяются стандартные аргументы против преждевременной оптимизации, но я не думаю, что вы действительно потеряете много ясности / прямолинейности, используя, re.compileесли вы подозреваете, что ваши регулярные выражения могут стать узким местом производительности.
Обновить:
Под Python 3.6 (я подозреваю, что вышеупомянутые тайминги были сделаны с использованием Python 2.x) и аппаратного обеспечения 2018 года (MacBook Pro), теперь я получаю следующие тайминги:
% python -m timeit -s "import re""re.match('hello', 'hello world')"1000000 loops, best of 3:0.661 usec per loop
% python -m timeit -s "import re; h=re.compile('hello')""h.match('hello world')"1000000 loops, best of 3:0.285 usec per loop
% python -m timeit -s "import re""h=re.compile('hello'); h.match('hello world')"1000000 loops, best of 3:0.65 usec per loop
% python --version
Python3.6.5::Anaconda,Inc.
Я также добавил случай (обратите внимание на различия в кавычках между двумя последними прогонами), который показывает, что re.match(x, ...)он буквально [приблизительно] эквивалентен re.compile(x).match(...), то есть, похоже, никакого закулисного кэширования скомпилированного представления не происходит.
Основные проблемы с вашей методологией здесь, поскольку аргумент настройки НЕ включается во время. Таким образом, вы удалили время компиляции из второго примера и просто усреднили его в первом примере. Это не значит, что первый пример компилируется каждый раз.
Триптих
1
Да, я согласен, что это несправедливое сравнение двух случаев.
Кив
7
Я понимаю, что вы имеете в виду, но разве это не то, что происходит в реальном приложении, где регулярное выражение используется много раз?
ДФ.
26
@Triptych, @Kiv: Смысл компиляции регулярных выражений отдельно от использования заключается в минимизации компиляции; удаление его из времени - это именно то, что dF должен был сделать, потому что он наиболее точно отражает реальное использование. Время компиляции особенно не имеет отношения к тому, как timeit.py выполняет свои настройки здесь; он выполняет несколько запусков и сообщает только самый короткий, после чего скомпилированное регулярное выражение кэшируется. Дополнительные затраты, которые вы видите здесь, это не стоимость компиляции регулярного выражения, а стоимость поиска его в скомпилированном кэше регулярного выражения (словаре).
Джемфинч
3
@Triptych Должен ли import reбыть выведен из установки? Это все о том, где вы хотите измерить. Если бы я запускал сценарий на Python много раз, это было бы import reударом по времени. При сравнении двух важно разделить две строки для определения времени. Да, как вы говорите, это когда у вас будет время. Сравнение показывает, что либо вы берете удар по времени один раз и повторяете меньшее попадание при компиляции, либо вы берете удар каждый раз, предполагая, что кэш очищается между вызовами, что, как было указано, может произойти. Добавление сроков h=re.compile('hello')поможет уточнить.
Том Мидделтын
39
Вот простой тестовый пример:
~$ for x in1101001000100001000001000000;do python -m timeit -n $x -s 'import re''re.match("[0-9]{3}-[0-9]{3}-[0-9]{4}", "123-123-1234")'; done
1 loops, best of 3:3.1 usec per loop
10 loops, best of 3:2.41 usec per loop
100 loops, best of 3:2.24 usec per loop
1000 loops, best of 3:2.21 usec per loop
10000 loops, best of 3:2.23 usec per loop
100000 loops, best of 3:2.24 usec per loop
1000000 loops, best of 3:2.31 usec per loop
с re.compile:
~$ for x in1101001000100001000001000000;do python -m timeit -n $x -s 'import re''r = re.compile("[0-9]{3}-[0-9]{3}-[0-9]{4}")''r.match("123-123-1234")'; done
1 loops, best of 3:1.91 usec per loop
10 loops, best of 3:0.691 usec per loop
100 loops, best of 3:0.701 usec per loop
1000 loops, best of 3:0.684 usec per loop
10000 loops, best of 3:0.682 usec per loop
100000 loops, best of 3:0.694 usec per loop
1000000 loops, best of 3:0.702 usec per loop
Таким образом, в этом простом случае может показаться, что компиляция выполняется быстрее, даже если вы совпадаете только один раз .
на самом деле это не имеет значения, смысл в том, чтобы попробовать эталонный тест в среде, в которой вы будете запускать код
Дэвид
1
Для меня производительность почти одинакова для 1000 и более петель. Скомпилированная версия быстрее на 1-100 циклов. (На обоих питонах 2.7 и 3.4).
Цитракс
2
На моей установке Python 2.7.3 почти нет разницы. Иногда компиляция происходит быстрее, иногда медленнее. Разница всегда <5%, поэтому я считаю разницу как погрешность измерения, поскольку устройство имеет только один процессор.
Даккарон
1
В Python 3.4.3 наблюдается два отдельных запуска: использование скомпилировано было даже медленнее, чем не скомпилировано.
Зельфир Кальцталь
17
Я просто попробовал это сам. Для простого случая разбора числа из строки и его суммирования использование скомпилированного объекта регулярного выражения примерно в два раза быстрее, чем использованиеre методов.
Как уже отмечали другие, reметоды (включая re.compile) ищут строку регулярного выражения в кэше ранее скомпилированных выражений. Следовательно, в обычном случае дополнительные расходы на использованиеre методов - это просто стоимость поиска в кэше.
Однако изучение кода показывает, что кеш ограничен 100 выражениями. Возникает вопрос: насколько больно переполнять кеш? Код содержит внутренний интерфейс к компилятору регулярных выражений re.sre_compile.compile. Если мы это называем, мы обходим кеш. Оказывается, что оно примерно на два порядка медленнее для основного регулярного выражения, такого какr'\w+\s+([0-9_]+)\s+\w*' .
Вот мой тест:
#!/usr/bin/env pythonimport re
import time
def timed(func):def wrapper(*args):
t = time.time()
result = func(*args)
t = time.time()- t
print'%s took %.3f seconds.'%(func.func_name, t)return result
return wrapper
regularExpression = r'\w+\s+([0-9_]+)\s+\w*'
testString ="average 2 never"@timeddef noncompiled():
a =0for x in xrange(1000000):
m = re.match(regularExpression, testString)
a += int(m.group(1))return a
@timeddef compiled():
a =0
rgx = re.compile(regularExpression)for x in xrange(1000000):
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef reallyCompiled():
a =0
rgx = re.sre_compile.compile(regularExpression)for x in xrange(1000000):
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef compiledInLoop():
a =0for x in xrange(1000000):
rgx = re.compile(regularExpression)
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef reallyCompiledInLoop():
a =0for x in xrange(10000):
rgx = re.sre_compile.compile(regularExpression)
m = rgx.match(testString)
a += int(m.group(1))return a
r1 = noncompiled()
r2 = compiled()
r3 = reallyCompiled()
r4 = compiledInLoop()
r5 = reallyCompiledInLoop()print"r1 = ", r1
print"r2 = ", r2
print"r3 = ", r3
print"r4 = ", r4
print"r5 = ", r5
</pre>And here is the output on my machine:<pre>
$ regexTest.py
noncompiled took 4.555 seconds.
compiled took 2.323 seconds.
reallyCompiled took 2.325 seconds.
compiledInLoop took 4.620 seconds.
reallyCompiledInLoop took 4.074 seconds.
r1 =2000000
r2 =2000000
r3 =2000000
r4 =2000000
r5 =20000
Методы «на самом деле скомпилированные» используют внутренний интерфейс, который обходит кеш. Обратите внимание, что тот, который компилируется на каждой итерации цикла, повторяется только 10 000 раз, а не один миллион.
Я согласен с вами, что скомпилированные регулярные выражения работают намного быстрее, чем не скомпилированные. Я выполнил более 10 000 предложений и создал цикл для повторения регулярных выражений, когда регулярные выражения не были скомпилированы и вычислялись каждый раз, когда прогноз полного цикла составлял 8 часов, после создания словаря по индексу с скомпилированными шаблонами регулярных выражений, которые я запускаю все это за 2 минуты. Я не могу понять ответы выше ...
Эли Бородач
12
Я согласен с Честным Абэ, что match(...)в приведенных примерах они разные. Они не являются взаимно-однозначными сравнениями и, следовательно, результаты различны. Чтобы упростить свой ответ, я использую A, B, C, D для этих функций. О да, мы имеем дело с 4 функциями вre.py вместо 3.
Запуск этого куска кода:
h = re.compile('hello')# (A)
h.match('hello world')# (B)
так же, как запуск этого кода:
re.match('hello','hello world')# (C)
Потому что, если посмотреть на источник re.py, (A + B) означает:
h = re._compile('hello')# (D)
h.match('hello world')
и (C) на самом деле:
re._compile('hello').match('hello world')
Таким образом, (C) не совпадает с (B). Фактически, (C) вызывает (B) после вызова (D), который также вызывается (A). Другими словами,(C) = (A) + (B) . Следовательно, сравнение (A + B) внутри цикла дает тот же результат, что и (C) внутри цикла.
Джордж regexTest.pyдоказал это для нас.
noncompiled took 4.555 seconds.# (C) in a loop
compiledInLoop took 4.620 seconds.# (A + B) in a loop
compiled took 2.323 seconds.# (A) once + (B) in a loop
Всем интересно, как получить результат 2.323 секунды. Чтобы убедиться, что вызывается compile(...)только один раз, нам нужно сохранить скомпилированный объект регулярного выражения в памяти. Если мы используем класс, мы могли бы сохранить объект и повторно использовать каждый раз, когда вызывается наша функция.
Если мы не используем класс (что является моей просьбой сегодня), то у меня нет комментариев. Я все еще учусь использовать глобальную переменную в Python, и я знаю, что глобальная переменная - это плохо.
Еще один момент, я считаю, что использование (A) + (B)подхода имеет преимущество. Вот некоторые факты, которые я заметил (поправьте меня, если я ошибаюсь):
Вызывает A один раз, он выполнит один поиск, _cacheа затем один, sre_compile.compile()чтобы создать объект регулярного выражения. Вызов A дважды, он выполнит два поиска и одну компиляцию (потому что объект регулярного выражения кэшируется).
Если _cacheпромежуточное значение сбрасывается, то объект регулярного выражения освобождается из памяти, и Python должен снова скомпилироваться. (кто-то предполагает, что Python не будет перекомпилирован.)
Если мы сохраним объект regex с помощью (A), объект regex все равно попадет в _cache и каким-то образом очистится. Но наш код сохраняет ссылку на него, и объект regex не будет освобожден из памяти. Те, Python не нужно компилировать снова.
Разница в 2 секунды в тесте Джорджа compiledInLoop vs compiled - это в основном время, необходимое для создания ключа и поиска в _cache. Это не означает время компиляции регулярных выражений.
По-настоящему тестовый тест Джорджа показывает, что произойдет, если он действительно будет повторять компиляцию каждый раз: это будет в 100 раз медленнее (он уменьшил цикл с 1 000 000 до 10 000).
Вот единственные случаи, когда (A + B) лучше, чем (C):
Если мы можем кэшировать ссылку на объект регулярного выражения внутри класса.
Если нам нужно вызывать (B) повторно (внутри цикла или несколько раз), мы должны кэшировать ссылку на объект регулярного выражения вне цикла.
Случай, который (C) достаточно хорош:
Мы не можем кэшировать ссылку.
Мы используем его только время от времени.
В целом, у нас не так уж много регулярных выражений (предположим, что скомпилированный никогда не сбрасывается)
Просто резюме, вот азбука:
h = re.compile('hello')# (A)
h.match('hello world')# (B)
re.match('hello','hello world')# (C)
Кроме того, re.compile () обходит лишнюю логику косвенного обращения и кеширования:
_cache ={}
_pattern_type = type(sre_compile.compile("",0))
_MAXCACHE =512def _compile(pattern, flags):# internal: compile patterntry:
p, loc = _cache[type(pattern), pattern, flags]if loc isNoneor loc == _locale.setlocale(_locale.LC_CTYPE):return p
exceptKeyError:passif isinstance(pattern, _pattern_type):if flags:raiseValueError("cannot process flags argument with a compiled pattern")return pattern
ifnot sre_compile.isstring(pattern):raiseTypeError("first argument must be string or compiled pattern")
p = sre_compile.compile(pattern, flags)ifnot(flags & DEBUG):if len(_cache)>= _MAXCACHE:
_cache.clear()if p.flags & LOCALE:ifnot _locale:return p
loc = _locale.setlocale(_locale.LC_CTYPE)else:
loc =None
_cache[type(pattern), pattern, flags]= p, loc
return p
В дополнение к небольшому выигрышу в скорости от использования re.compile , людям также нравится удобочитаемость, возникающая из-за именования потенциально сложных спецификаций шаблонов и отделения их от бизнес-логики, где они применяются:
Обратите внимание, еще один респондент ошибочно полагал, что pyc- файлы хранят скомпилированные шаблоны напрямую; однако в действительности они перестраиваются каждый раз, когда загружается PYC:
это "в def search(pattern, string, flags=0):"опечатка?
phuclv
1
Обратите внимание, что если patternэто уже скомпилированный шаблон, затраты на кэширование становятся значительными: хеширование a обходится SRE_Patternдорого, и шаблон никогда не записывается в кэш, поэтому поиск каждый раз завершается ошибкой с помощью a KeyError.
Эрик
5
В общем, я считаю, что легче использовать флаги (по крайней мере, легче запомнить, как), как re.Iпри компиляции шаблонов, чем использовать встроенные флаги.
Необязательный второй параметр pos дает индекс в строке, с которой начинается поиск; по умолчанию это 0. Это не полностью эквивалентно разрезанию строки; '^'шаблон символы соответствуют в реальном начале строки и в позициях сразу после символа новой строки, но не обязательно в индексе , где начинается поиск.
endpos
Необязательный параметр endpos ограничивает область поиска строки; это будет как если строка endpos символы, поэтому только персонажи из поз в endpos - 1будут искать совпадения. Если endpos меньше pos , совпадение не будет найдено; в противном случае, если rx является скомпилированным объектом регулярного выражения, rx.search(string, 0,
50)эквивалентно rx.search(string[:50], 0).
Методы search , findall и finditer объекта regex также поддерживают эти параметры.
Значение pos, которое было передано в метод search () или match () объекта регулярного выражения. Это указатель на строку, в которой механизм RE начал поиск совпадения.
match.endpos
Значение endpos, которое было передано в метод search () или match () объекта regex. Это указатель на строку, за которую двигатель RE не пойдет.
Словарь, отображающий любые символические имена групп, определенные (? P), на номера групп. Словарь является пустым, если в шаблоне не использовались символические группы.
Помимо различия в производительности, использование re.compile и использование скомпилированного объекта регулярного выражения для сопоставления (независимо от операций, связанных с регулярным выражением) делает семантику более понятной для среды выполнения Python.
У меня был болезненный опыт отладки простого кода:
compare =lambda s, p: re.match(p, s)
а позже я бы использовал сравнение в
[x for x in data if compare(patternPhrases, x[columnIndex])]
где patternPhrasesдолжна быть переменная, содержащая строку регулярного выражения,x[columnIndex] переменная, содержащая строку.
У меня были проблемы, которые patternPhrasesне соответствовали ожидаемой строке!
Но если бы я использовал форму re.compile:
compare =lambda s, p: p.match(s)
затем в
[x for x in data if compare(patternPhrases, x[columnIndex])]
Python жаловался бы на то, что «строка не имеет атрибута соответствия», как при сопоставлении позиционных аргументов compare, x[columnIndex]используется как регулярное выражение !, когда я на самом деле имел в виду
compare =lambda p, s: p.match(s)
В моем случае использование re.compile более явно относится к цели регулярного выражения, когда его значение скрыто невооруженным глазом, поэтому я мог бы получить дополнительную помощь от проверки во время выполнения Python.
Итак, мораль моего урока заключается в том, что когда регулярное выражение - это не просто буквальная строка, я должен использовать re.compile, чтобы Python помог мне подтвердить мои предположения.
Существует одно дополнительное преимущество использования re.compile () в форме добавления комментариев к моим шаблонам регулярных выражений с использованием re.VERBOSE.
pattern ='''
hello[ ]world # Some info on my pattern logic. [ ] to recognize space
'''
re.search(pattern,'hello world', re.VERBOSE)
Хотя это не влияет на скорость выполнения вашего кода, мне нравится делать это таким образом, поскольку это является частью моей привычки комментировать. Мне совершенно не нравится тратить время, пытаясь вспомнить логику, которая стояла за моим кодом через 2 месяца, когда я хочу внести изменения.
Я отредактировал твой ответ. Я думаю, что упоминание re.VERBOSEимеет смысл, и оно добавляет то, что другие ответы, кажется, не учли. Однако если вы ответите «Я пишу здесь, потому что я пока не могу комментировать», то обязательно удалите его. Пожалуйста, не используйте поле для ответов ни для чего, кроме ответов. Вы только один или два хороших ответа от возможности комментировать в любом месте (50 повторений), поэтому, пожалуйста, будьте терпеливы. Помещая комментарии в поля для ответов, когда вы знаете, что не должны, вы быстрее туда не попадете. Это даст вам отрицательные отзывы и удаленные ответы.
prog = re.compile(pattern)
result = prog.match(string)
эквивалентно
result = re.match(pattern, string)
но используя re.compile() и сохранение полученного объекта регулярного выражения для повторного использования более эффективно, когда выражение будет использоваться несколько раз в одной программе.
Итак, мой вывод: если вы собираетесь сопоставить один и тот же шаблон для множества разных текстов, вам лучше его предварительно скомпилировать.
Интересно, что компиляция оказывается более эффективной для меня (Python 2.5.2 на Win XP):
import re
import time
rgx = re.compile('(\w+)\s+[0-9_]?\s+\w*')
str ="average 2 never"
a =0
t = time.time()for i in xrange(1000000):if re.match('(\w+)\s+[0-9_]?\s+\w*', str):#~ if rgx.match(str):
a +=1print time.time()- t
Выполнение приведенного выше кода один раз, как есть, и один раз с двумя ifстроками, прокомментированными наоборот, скомпилированное регулярное выражение в два раза быстрее
Та же проблема, что и при сравнении производительности dF. Это не совсем справедливо, если вы не включите затраты на производительность самого оператора компиляции.
Карл Мейер
6
Карл, я не согласен. Компиляция выполняется только один раз, а соответствующий цикл выполняется миллион раз
Эли Бендерский,
@eliben: я согласен с Карлом Мейером. Компиляция происходит в обоих случаях. Триптих упоминает, что кеширование задействовано, поэтому в оптимальном случае (остается в кеше) оба подхода O (n + 1), хотя часть +1 скрыта, если вы не используете re.compile явно.
паприка
1
Не пишите свой собственный код для тестирования. Научитесь использовать timeit.py, который входит в стандартный дистрибутив.
Джемфинч
Сколько времени вы воссоздаете строку шаблона в цикле for. Эти накладные расходы не могут быть тривиальными.
IceArdor
3
Я провел этот тест, прежде чем наткнуться на обсуждение здесь. Однако, запустив его, я решил опубликовать результаты.
Я украл и убрал пример из «Мастеринг регулярных выражений» Джеффа Фридла. Это на MacBook с OSX 10.6 (2 ГГц Intel Core 2 Duo, 4 ГБ оперативной памяти). Версия Python 2.6.1.
Выполнить 1 - используя re.compile
import re
import time
import fpformat
Regex1= re.compile('^(a|b|c|d|e|f|g)+$')Regex2= re.compile('^[a-g]+$')TimesToDo=1000TestString=""for i in range(1000):TestString+="abababdedfg"StartTime= time.time()for i in range(TimesToDo):Regex1.search(TestString)Seconds= time.time()-StartTimeprint"Alternation takes "+ fpformat.fix(Seconds,3)+" seconds"StartTime= time.time()for i in range(TimesToDo):Regex2.search(TestString)Seconds= time.time()-StartTimeprint"Character Class takes "+ fpformat.fix(Seconds,3)+" seconds"Alternation takes 2.299 seconds
CharacterClass takes 0.107 seconds
Run 2 - не использовать re.compile
import re
import time
import fpformat
TimesToDo=1000TestString=""for i in range(1000):TestString+="abababdedfg"StartTime= time.time()for i in range(TimesToDo):
re.search('^(a|b|c|d|e|f|g)+$',TestString)Seconds= time.time()-StartTimeprint"Alternation takes "+ fpformat.fix(Seconds,3)+" seconds"StartTime= time.time()for i in range(TimesToDo):
re.search('^[a-g]+$',TestString)Seconds= time.time()-StartTimeprint"Character Class takes "+ fpformat.fix(Seconds,3)+" seconds"Alternation takes 2.508 seconds
CharacterClass takes 0.109 seconds
Этот ответ может опоздать, но это интересная находка. Использование компиляции может реально сэкономить ваше время, если вы планируете использовать регулярные выражения несколько раз (это также упоминается в документации). Ниже вы можете увидеть, что использование скомпилированного регулярного выражения является самым быстрым, когда к нему напрямую вызывается метод match. передача скомпилированного регулярного выражения в re.match делает его еще медленнее, а передача re.match со строкой скороговорки находится где-то посередине.
В качестве дополнения я сделал исчерпывающую таблицу модулей reдля вашей справки.
regex ={'brackets':{'single_character':['[]','.',{'negate':'^'}],'capturing_group':['()','(?:)','(?!)''|','\\','backreferences and named group'],'repetition':['{}','*?','+?','??','greedy v.s. lazy ?']},'lookaround':{'lookahead':['(?=...)','(?!...)'],'lookbehind':['(?<=...)','(?<!...)'],'caputuring':['(?P<name>...)','(?P=name)','(?:)'],},'escapes':{'anchor':['^','\b','$'],'non_printable':['\n','\t','\r','\f','\v'],'shorthand':['\d','\w','\s']},'methods':{['search','match','findall','finditer'],['split','sub']},'match_object':['group','groups','groupdict','start','end','span',]}
Я действительно уважаю все вышеперечисленные ответы. С моего мнения да! Наверняка стоит использовать re.compile вместо компиляции регулярных выражений, снова и снова, каждый раз.
Использование re.compile делает ваш код более динамичным, так как вы можете вызывать уже скомпилированное регулярное выражение вместо повторной компиляции и повторного ввода. Эта вещь приносит вам пользу в случаях:
Усилие процессора
Сложность времени.
Делает регулярное выражение универсальным. (Может использоваться в findall, search, match)
И делает вашу программу выглядит круто.
Пример :
example_string ="The room number of her room is 26A7B."
find_alpha_numeric_string = re.compile(r"\b\w+\b")
Использование в Findall
find_alpha_numeric_string.findall(example_string)
Использование в поиске
find_alpha_numeric_string.search(example_string)
Точно так же вы можете использовать его для: совпадения и замены
Это хороший вопрос. Вы часто видите, как люди используют re.compile без причины. Это уменьшает читабельность. Но уверен, что во многих случаях требуется предварительная компиляция выражения. Например, когда вы используете это многократно в цикле или что-то подобное.
Это как все в программировании (все в жизни на самом деле). Примените здравый смысл.
Насколько я могу судить по краткому обзору, Python в двух словах не упоминает об использовании без re.compile (), что меня заинтересовало.
Мат
Объект регулярного выражения добавляет еще один объект в контекст. Как я уже сказал, существует много ситуаций, когда re.compile () имеет свое место. Пример, приведенный ФП, не является одним из них.
PEZ
1
(несколько месяцев спустя) легко добавить свой собственный кеш вокруг re.match, или что-нибудь еще в этом отношении -
У меня был большой опыт запуска скомпилированного регулярного выражения в 1000 раз по сравнению с компиляцией на лету, и я не заметил какой-либо ощутимой разницы
Голосование за принятый ответ приводит к предположению, что то, что говорит @Triptych, верно для всех случаев. Это не обязательно правда. Одно большое отличие заключается в том, что вам нужно решить, принимать ли строку регулярного выражения или скомпилированный объект регулярного выражения в качестве параметра функции:
>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: x.match(y) # accepts compiled regex as parameter
... h=re.compile('hello')
... """, stmt="f(h, 'hello world')")0.32881879806518555>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: re.compile(x).match(y) # compiles when called
... """, stmt="f('hello', 'hello world')")0.809190034866333
Всегда лучше скомпилировать свои регулярные выражения на случай, если вам потребуется их повторно использовать.
Обратите внимание, что в приведенном выше примере время имитирует создание скомпилированного объекта регулярного выражения один раз во время импорта, а не «на лету», когда это требуется для сопоставления.
В качестве альтернативного ответа, поскольку я вижу, что он не упоминался ранее, я приведу цитаты из документов по Python 3 :
Должны ли вы использовать эти функции уровня модуля, или вы должны получить шаблон и вызывать его методы самостоятельно? Если вы обращаетесь к регулярному выражению в цикле, его предварительная компиляция сохранит несколько вызовов функций. Вне циклов нет особой разницы благодаря внутреннему кешу.
Вот пример, где использование re.compileболее чем в 50 раз быстрее, чем требуется .
Дело в том же, что и в приведенном выше комментарии, а именно, использование re.compileможет быть значительным преимуществом, если вы используете его так, что не получаете больших выгод от кэша компиляции. Это происходит по крайней мере в одном конкретном случае (с которым я столкнулся на практике), а именно, когда все следующее верно:
У вас есть много шаблонов регулярных выражений (больше re._MAXCACHE, чей по умолчанию в настоящее время 512), и
Вы используете эти регулярные выражения много раз, и
Ваши последовательные использования одного и того же шаблона разделены большим количеством re._MAXCACHEдругих регулярных выражений между ними, так что каждый из них выгружается из кэша между последовательными использованиями.
import re
import time
def setup(N=1000):# Patterns 'a.*a', 'a.*b', ..., 'z.*z'
patterns =[chr(i)+'.*'+ chr(j)for i in range(ord('a'), ord('z')+1)for j in range(ord('a'), ord('z')+1)]# If this assertion below fails, just add more (distinct) patterns.# assert(re._MAXCACHE < len(patterns))# N strings. Increase N for larger effect.
strings =['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz']* N
return(patterns, strings)def without_compile():print('Without re.compile:')
patterns, strings = setup()print('searching')
count =0for s in strings:for pat in patterns:
count += bool(re.search(pat, s))return count
def without_compile_cache_friendly():print('Without re.compile, cache-friendly order:')
patterns, strings = setup()print('searching')
count =0for pat in patterns:for s in strings:
count += bool(re.search(pat, s))return count
def with_compile():print('With re.compile:')
patterns, strings = setup()print('compiling')
compiled =[re.compile(pattern)for pattern in patterns]print('searching')
count =0for s in strings:for regex in compiled:
count += bool(regex.search(s))return count
start = time.time()print(with_compile())
d1 = time.time()- start
print(f'-- That took {d1:.2f} seconds.\n')
start = time.time()print(without_compile_cache_friendly())
d2 = time.time()- start
print(f'-- That took {d2:.2f} seconds.\n')
start = time.time()print(without_compile())
d3 = time.time()- start
print(f'-- That took {d3:.2f} seconds.\n')print(f'Ratio: {d3/d1:.2f}')
Пример вывода, который я получаю на своем ноутбуке (Python 3.7.7):
With re.compile:
compiling
searching
676000
-- That took 0.33 seconds.
Without re.compile, cache-friendly order:
searching
676000
-- That took 0.67 seconds.
Without re.compile:
searching
676000
-- That took 23.54 seconds.
Ratio: 70.89
Я не стал беспокоиться, так timeitкак разница такая большая, но я получаю качественно похожие цифры каждый раз. Обратите внимание, что даже без re.compileиспользования одного и того же регулярного выражения несколько раз и переходя к следующему не было так уж плохо (только примерно в 2 раза медленнее, чем с re.compile), но в другом порядке (циклически повторяя много регулярных выражений) это значительно хуже , как и ожидалось. Кроме того , увеличение размера кэша тоже работает: просто установив re._MAXCACHE = len(patterns)в setup()выше (конечно , я не рекомендую делать такие вещи , в производстве , как имена с подчеркиванием условно «частными») падает на ~ 23 секунды до ~ 0,7 секунды, что также соответствует нашему пониманию.
PS: если я использую только 3 шаблона регулярных выражений во всем коде, каждый из которых использовал (без какого-либо определенного порядка) сотни раз, кэш регулярных выражений будет автоматически сохранять скомпилированное регулярное выражение, верно?
Басж
@Basj Я думаю, вы могли бы просто попробовать и посмотреть :) Но ответ, я уверен, да: единственная дополнительная стоимость в этом случае AFAICT - это просто поиск шаблона в кэше . Также обратите внимание, что кэш является глобальным (на уровне модуля), поэтому в принципе у вас может быть некоторая библиотека зависимостей, выполняющая поиск по регулярному выражению между вашими, поэтому трудно быть полностью уверенным, что ваша программа когда-либо использует только 3 (или любое другое число) регулярного выражения. шаблоны, но было бы довольно странно быть иначе :)
ShreevatsaR
0
Регулярные выражения компилируются перед использованием при использовании второй версии. Если вы собираетесь выполнять его много раз, определенно лучше сначала скомпилировать его. Если не компилировать каждый раз, когда вы подходите для одного, это хорошо.
Для меня главный выигрыш в том , что мне нужно только помнить, и читать, одну форму осложненного регулярных выражений синтаксис API - в <compiled_pattern>.method(xxx)форме , а не что и в re.func(<pattern>, xxx)форме.
Это re.compile(<pattern>)немного лишний шаблон, правда.
Но в том, что касается регулярных выражений, этот дополнительный шаг компиляции вряд ли будет большой причиной когнитивной нагрузки. И на самом деле, на сложных шаблонах вы могли бы даже получить ясность, отделив объявление от любого метода регулярного выражения, который затем вызываете для него.
Я склонен сначала настраивать сложные шаблоны на таком веб-сайте, как Regex101, или даже в отдельном минимальном тестовом сценарии, а затем вносить их в мой код, поэтому отделение объявления от его использования также подходит моему рабочему процессу.
Я хотел бы мотивировать, что предварительная компиляция является и концептуальной, и «буквально» (как в «грамотном программировании») выгодной. взгляните на этот фрагмент кода:
from re import compile as_Reclass TYPO:def text_has_foobar( self, text ):return self._text_has_foobar_re_search( text )isnotNone
_text_has_foobar_re_search =_Re( r"""(?i)foobar""").search
TYPO = TYPO()
в своем заявлении вы бы написали:
from TYPO import TYPO
print( TYPO.text_has_foobar('FOObar ) )
это примерно настолько просто с точки зрения функциональности, насколько это возможно. потому что этот пример очень короткий, я нашел способ объединить _text_has_foobar_re_searchвсе в одну строку. недостаток этого кода в том, что он занимает мало памяти для любого времени жизни TYPOбиблиотечного объекта; Преимущество состоит в том, что при выполнении поиска в foobar вы получите два вызова функций и два поиска в словаре классов. сколько регулярных выражений кэшируется, reи издержки этого кэша здесь не имеют значения.
сравните это с более привычным стилем ниже:
import re
classTypo:def text_has_foobar( self, text ):return re.compile( r"""(?i)foobar""").search( text )isnotNone
Я с готовностью признаю, что мой стиль весьма необычен для питона, возможно, даже спорен. однако в примере, который более точно соответствует тому, как Python в основном используется, для того, чтобы выполнить одно совпадение, мы должны создать экземпляр объекта, выполнить три поиска в словаре экземпляра и выполнить три вызова функций; Кроме того, мы могли бы попасть вre столкнуться с проблемами кэширования при использовании более 100 регулярных выражений. Кроме того, регулярное выражение скрывается внутри тела метода, что в большинстве случаев не очень хорошая идея.
Следует сказать, что каждый набор мер - целевые, псевдонимы импортных заявлений; методы с псевдонимами, где это применимо; сокращение вызовов функций и поиска в словаре объектов --- может помочь уменьшить вычислительную и концептуальную сложность.
WTF. Не только ты откопал старый, отвеченный вопрос. Ваш код не является идиоматическим и неправильным на многих уровнях - (ab) использование классов в качестве пространств имен, где достаточно модуля, использование заглавных букв классов и т. Д. См. Pastebin.com/iTAXAWen для лучших реализаций. Не говоря уже о том, что используемое вами регулярное выражение нарушено. Всего -1
2
виновным. это старый вопрос, но я не возражаю против того, чтобы быть # 100 в замедленном разговоре. вопрос не был закрыт. Я предупреждал, что мой код может быть противником некоторых вкусов. я думаю, если бы вы могли рассматривать это как простую демонстрацию того, что выполнимо в python, например: если мы берем все, все, во что мы верим, как необязательные, а затем объединяем все, что угодно, как выглядят вещи, которые мы можем получить? Я уверен, что вы можете различить достоинства и недостатки этого решения и можете жаловаться более четко. в противном случае я должен заключить, что ваше утверждение о неправоте основано на чуть большем, чем PEP008
поток
2
Нет, дело не в PEP8. Это просто соглашения об именах, и я бы никогда не понизил голосование за их несоблюдение. Я отказался от вас, потому что код, который вы показали, просто плохо написан. Он игнорирует условные обозначения и идиомы без всякой причины и является воплощением преждевременной оптимизации: вам придется оптимизировать дневной свет из всего остального кода, чтобы это стало узким местом, и даже тогда третье предложенное мной переписывание короче, более идиоматично и так же быстро по вашим рассуждениям (такое же количество атрибутов доступа).
«плохо написано» - как, почему именно? «бросает вызов соглашениям и идиомам», - предупредил я вас. «без причины» - да, у меня есть причина: упрощать, когда сложность не имеет смысла; «воплощение преждевременной оптимизации» - я очень за стиль программирования, который выбирает баланс читабельности и эффективности; ОП попросил выявить «преимущество использования re.compile», что я понимаю как вопрос об эффективности. «(ab) использование классов в качестве пространств имен» - это ваши слова оскорбительны. класс есть, так что у вас есть «я» точка отсчета. Я пытался использовать модули для этой цели, классы работают лучше.
поток
«Использование заглавных букв в именах классов», «Нет, дело не в PEP8» - вы, очевидно, настолько безумно сердиты, что даже не можете сказать, что спорить в первую очередь. "WTF", " неправильно " --- видите, как вы эмоциональны? больше объективности и меньше пены, пожалуйста.
поток
-5
Насколько я понимаю, эти два примера фактически эквивалентны. Единственное отличие состоит в том, что в первом вы можете повторно использовать скомпилированное регулярное выражение в другом месте, не вызывая его повторную компиляцию.
Вызов функции поиска объекта скомпилированного шаблона со строкой «M» выполняет то же самое, что и вызов re.search как с регулярным выражением, так и со строкой «M». Только намного, намного быстрее. (На самом деле, функция re.search просто компилирует регулярное выражение и вызывает для вас метод поиска результирующего объекта шаблона.)
re.sub
не будет аргумента флагов ...re.compile
дало улучшение в 10-50 раз. Мораль такова: если у вас есть много регулярных выражений (больше, чем MAXCACHE = 100), и вы используете их много раз каждый (и разделены более чем двумя регулярными выражениями MAXCACHE между ними, так что каждый из них сбрасывается из кэша: используйте то же самое много раз, а затем переход к следующему не считается), тогда это определенно поможет скомпилировать их. В противном случае это не имеет значения.in
проверка подстрок строки выполняется НАМНОГО быстрее:>python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 1.41 usec per loop
>python -m timeit "x = 'hello' in 'hello world'" 10000000 loops, best of 3: 0.0513 usec per loop
Ответы:
У меня был большой опыт работы с скомпилированным регулярным выражением 1000 раз по сравнению с компиляцией на лету, и я не заметил какой-либо ощутимой разницы. Очевидно, что это анекдотично, и, конечно, это не лучший аргумент против компиляции, но я обнаружил, что разница незначительна.
РЕДАКТИРОВАТЬ: После быстрого взгляда на фактический код библиотеки Python 2.5, я вижу, что Python внутренне компилирует и кэширует регулярные выражения всякий раз, когда вы используете их в любом случае (включая вызовы
re.match()
), так что вы действительно изменяете только КОГДА регулярное выражение компилируется, и не должно ' не экономить много времени - только время, необходимое для проверки кэша (поиск ключа по внутреннемуdict
типу).Из модуля re.py (комментарии мои):
Я до сих пор часто предварительно компилирую регулярные выражения, но только для того, чтобы связать их с красивым, многократно используемым именем, а не для ожидаемого увеличения производительности.
источник
Для меня самое большое преимущество
re.compile
является возможность отделить определение регулярного выражения от его использования.Даже простое выражение, такое как
0|[1-9][0-9]*
(целое число в основании 10 без начальных нулей), может быть достаточно сложным, чтобы вам не пришлось его перепечатывать, проверять, не были ли сделаны опечатки, а позже придется перепроверять наличие опечаток при запуске отладки. , Кроме того, лучше использовать имя переменной, например, num или num_b10, чем0|[1-9][0-9]*
.Конечно, можно хранить строки и передавать их в re.match; однако, это менее читабельно:
По сравнению с компиляцией:
Хотя это довольно близко, последняя строка второй кажется более естественной и простой при повторном использовании.
источник
us_phone_number
иsocial_security_number
т. Д.FWIW:
поэтому, если вы собираетесь часто использовать одно и то же регулярное выражение, это может стоить того
re.compile
(особенно для более сложных регулярных выражений).Применяются стандартные аргументы против преждевременной оптимизации, но я не думаю, что вы действительно потеряете много ясности / прямолинейности, используя,
re.compile
если вы подозреваете, что ваши регулярные выражения могут стать узким местом производительности.Обновить:
Под Python 3.6 (я подозреваю, что вышеупомянутые тайминги были сделаны с использованием Python 2.x) и аппаратного обеспечения 2018 года (MacBook Pro), теперь я получаю следующие тайминги:
Я также добавил случай (обратите внимание на различия в кавычках между двумя последними прогонами), который показывает, что
re.match(x, ...)
он буквально [приблизительно] эквивалентенre.compile(x).match(...)
, то есть, похоже, никакого закулисного кэширования скомпилированного представления не происходит.источник
import re
быть выведен из установки? Это все о том, где вы хотите измерить. Если бы я запускал сценарий на Python много раз, это было быimport re
ударом по времени. При сравнении двух важно разделить две строки для определения времени. Да, как вы говорите, это когда у вас будет время. Сравнение показывает, что либо вы берете удар по времени один раз и повторяете меньшее попадание при компиляции, либо вы берете удар каждый раз, предполагая, что кэш очищается между вызовами, что, как было указано, может произойти. Добавление сроковh=re.compile('hello')
поможет уточнить.Вот простой тестовый пример:
с re.compile:
Таким образом, в этом простом случае может показаться, что компиляция выполняется быстрее, даже если вы совпадаете только один раз .
источник
Я просто попробовал это сам. Для простого случая разбора числа из строки и его суммирования использование скомпилированного объекта регулярного выражения примерно в два раза быстрее, чем использование
re
методов.Как уже отмечали другие,
re
методы (включаяre.compile
) ищут строку регулярного выражения в кэше ранее скомпилированных выражений. Следовательно, в обычном случае дополнительные расходы на использованиеre
методов - это просто стоимость поиска в кэше.Однако изучение кода показывает, что кеш ограничен 100 выражениями. Возникает вопрос: насколько больно переполнять кеш? Код содержит внутренний интерфейс к компилятору регулярных выражений
re.sre_compile.compile
. Если мы это называем, мы обходим кеш. Оказывается, что оно примерно на два порядка медленнее для основного регулярного выражения, такого какr'\w+\s+([0-9_]+)\s+\w*'
.Вот мой тест:
Методы «на самом деле скомпилированные» используют внутренний интерфейс, который обходит кеш. Обратите внимание, что тот, который компилируется на каждой итерации цикла, повторяется только 10 000 раз, а не один миллион.
источник
Я согласен с Честным Абэ, что
match(...)
в приведенных примерах они разные. Они не являются взаимно-однозначными сравнениями и, следовательно, результаты различны. Чтобы упростить свой ответ, я использую A, B, C, D для этих функций. О да, мы имеем дело с 4 функциями вre.py
вместо 3.Запуск этого куска кода:
так же, как запуск этого кода:
Потому что, если посмотреть на источник
re.py
, (A + B) означает:и (C) на самом деле:
Таким образом, (C) не совпадает с (B). Фактически, (C) вызывает (B) после вызова (D), который также вызывается (A). Другими словами,
(C) = (A) + (B)
. Следовательно, сравнение (A + B) внутри цикла дает тот же результат, что и (C) внутри цикла.Джордж
regexTest.py
доказал это для нас.Всем интересно, как получить результат 2.323 секунды. Чтобы убедиться, что вызывается
compile(...)
только один раз, нам нужно сохранить скомпилированный объект регулярного выражения в памяти. Если мы используем класс, мы могли бы сохранить объект и повторно использовать каждый раз, когда вызывается наша функция.Если мы не используем класс (что является моей просьбой сегодня), то у меня нет комментариев. Я все еще учусь использовать глобальную переменную в Python, и я знаю, что глобальная переменная - это плохо.
Еще один момент, я считаю, что использование
(A) + (B)
подхода имеет преимущество. Вот некоторые факты, которые я заметил (поправьте меня, если я ошибаюсь):Вызывает A один раз, он выполнит один поиск,
_cache
а затем один,sre_compile.compile()
чтобы создать объект регулярного выражения. Вызов A дважды, он выполнит два поиска и одну компиляцию (потому что объект регулярного выражения кэшируется).Если
_cache
промежуточное значение сбрасывается, то объект регулярного выражения освобождается из памяти, и Python должен снова скомпилироваться. (кто-то предполагает, что Python не будет перекомпилирован.)Если мы сохраним объект regex с помощью (A), объект regex все равно попадет в _cache и каким-то образом очистится. Но наш код сохраняет ссылку на него, и объект regex не будет освобожден из памяти. Те, Python не нужно компилировать снова.
Разница в 2 секунды в тесте Джорджа compiledInLoop vs compiled - это в основном время, необходимое для создания ключа и поиска в _cache. Это не означает время компиляции регулярных выражений.
По-настоящему тестовый тест Джорджа показывает, что произойдет, если он действительно будет повторять компиляцию каждый раз: это будет в 100 раз медленнее (он уменьшил цикл с 1 000 000 до 10 000).
Вот единственные случаи, когда (A + B) лучше, чем (C):
Случай, который (C) достаточно хорош:
Просто резюме, вот азбука:
Спасибо за чтение.
источник
В основном, нет разницы, используете ли вы re.compile или нет. Внутри все функции реализованы в виде этапа компиляции:
Кроме того, re.compile () обходит лишнюю логику косвенного обращения и кеширования:
В дополнение к небольшому выигрышу в скорости от использования re.compile , людям также нравится удобочитаемость, возникающая из-за именования потенциально сложных спецификаций шаблонов и отделения их от бизнес-логики, где они применяются:
Обратите внимание, еще один респондент ошибочно полагал, что pyc- файлы хранят скомпилированные шаблоны напрямую; однако в действительности они перестраиваются каждый раз, когда загружается PYC:
Вышеприведенная разборка происходит из PYC-файла,
tmp.py
содержащего:источник
"
вdef search(pattern, string, flags=0):"
опечатка?pattern
это уже скомпилированный шаблон, затраты на кэширование становятся значительными: хеширование a обходитсяSRE_Pattern
дорого, и шаблон никогда не записывается в кэш, поэтому поиск каждый раз завершается ошибкой с помощью aKeyError
.В общем, я считаю, что легче использовать флаги (по крайней мере, легче запомнить, как), как
re.I
при компиляции шаблонов, чем использовать встроенные флаги.против
источник
re.findall
любом случае, вы можете использовать флаги в качестве третьего аргумента .Используя приведенные примеры:
Метод match в приведенном выше примере отличается от того, который используется ниже:
re.compile () возвращает объект регулярного выражения , что означает
h
объект регулярного выражения.У объекта regex есть собственный метод match с необязательными параметрами pos и endpos :
regex.match(string[, pos[, endpos]])
позиция
endpos
Методы search , findall и finditer объекта regex также поддерживают эти параметры.
re.match(pattern, string, flags=0)
не поддерживает их, как вы можете видеть,а также их поиск , поиск и поиск аналоги .
У совпадающего объекта есть атрибуты, которые дополняют эти параметры:
match.pos
match.endpos
Объект регулярного выражения имеет два уникальных, возможно, полезных атрибута:
regex.groups
regex.groupindex
И, наконец, объект сопоставления имеет этот атрибут:
match.re
источник
Помимо различия в производительности, использование re.compile и использование скомпилированного объекта регулярного выражения для сопоставления (независимо от операций, связанных с регулярным выражением) делает семантику более понятной для среды выполнения Python.
У меня был болезненный опыт отладки простого кода:
а позже я бы использовал сравнение в
где
patternPhrases
должна быть переменная, содержащая строку регулярного выражения,x[columnIndex]
переменная, содержащая строку.У меня были проблемы, которые
patternPhrases
не соответствовали ожидаемой строке!Но если бы я использовал форму re.compile:
затем в
Python жаловался бы на то, что «строка не имеет атрибута соответствия», как при сопоставлении позиционных аргументов
compare
,x[columnIndex]
используется как регулярное выражение !, когда я на самом деле имел в видуВ моем случае использование re.compile более явно относится к цели регулярного выражения, когда его значение скрыто невооруженным глазом, поэтому я мог бы получить дополнительную помощь от проверки во время выполнения Python.
Итак, мораль моего урока заключается в том, что когда регулярное выражение - это не просто буквальная строка, я должен использовать re.compile, чтобы Python помог мне подтвердить мои предположения.
источник
Существует одно дополнительное преимущество использования re.compile () в форме добавления комментариев к моим шаблонам регулярных выражений с использованием re.VERBOSE.
Хотя это не влияет на скорость выполнения вашего кода, мне нравится делать это таким образом, поскольку это является частью моей привычки комментировать. Мне совершенно не нравится тратить время, пытаясь вспомнить логику, которая стояла за моим кодом через 2 месяца, когда я хочу внести изменения.
источник
re.VERBOSE
имеет смысл, и оно добавляет то, что другие ответы, кажется, не учли. Однако если вы ответите «Я пишу здесь, потому что я пока не могу комментировать», то обязательно удалите его. Пожалуйста, не используйте поле для ответов ни для чего, кроме ответов. Вы только один или два хороших ответа от возможности комментировать в любом месте (50 повторений), поэтому, пожалуйста, будьте терпеливы. Помещая комментарии в поля для ответов, когда вы знаете, что не должны, вы быстрее туда не попадете. Это даст вам отрицательные отзывы и удаленные ответы.Согласно документации Python :
Последовательность
эквивалентно
но используя
re.compile()
и сохранение полученного объекта регулярного выражения для повторного использования более эффективно, когда выражение будет использоваться несколько раз в одной программе.Итак, мой вывод: если вы собираетесь сопоставить один и тот же шаблон для множества разных текстов, вам лучше его предварительно скомпилировать.
источник
Интересно, что компиляция оказывается более эффективной для меня (Python 2.5.2 на Win XP):
Выполнение приведенного выше кода один раз, как есть, и один раз с двумя
if
строками, прокомментированными наоборот, скомпилированное регулярное выражение в два раза быстрееисточник
Я провел этот тест, прежде чем наткнуться на обсуждение здесь. Однако, запустив его, я решил опубликовать результаты.
Я украл и убрал пример из «Мастеринг регулярных выражений» Джеффа Фридла. Это на MacBook с OSX 10.6 (2 ГГц Intel Core 2 Duo, 4 ГБ оперативной памяти). Версия Python 2.6.1.
Выполнить 1 - используя re.compile
Run 2 - не использовать re.compile
источник
Этот ответ может опоздать, но это интересная находка. Использование компиляции может реально сэкономить ваше время, если вы планируете использовать регулярные выражения несколько раз (это также упоминается в документации). Ниже вы можете увидеть, что использование скомпилированного регулярного выражения является самым быстрым, когда к нему напрямую вызывается метод match. передача скомпилированного регулярного выражения в re.match делает его еще медленнее, а передача re.match со строкой скороговорки находится где-то посередине.
источник
Помимо производительности.
Использование
compile
помогает мне различать понятия:1. module (re) ,
2. regex object
3. match object
Когда я начал изучать regex
В качестве дополнения я сделал исчерпывающую таблицу модулей
re
для вашей справки.источник
Я действительно уважаю все вышеперечисленные ответы. С моего мнения да! Наверняка стоит использовать re.compile вместо компиляции регулярных выражений, снова и снова, каждый раз.
Пример :
Использование в Findall
Использование в поиске
источник
Это хороший вопрос. Вы часто видите, как люди используют re.compile без причины. Это уменьшает читабельность. Но уверен, что во многих случаях требуется предварительная компиляция выражения. Например, когда вы используете это многократно в цикле или что-то подобное.
Это как все в программировании (все в жизни на самом деле). Примените здравый смысл.
источник
(несколько месяцев спустя) легко добавить свой собственный кеш вокруг re.match, или что-нибудь еще в этом отношении -
Wibni, было бы неплохо, если: cachehint (size =), cacheinfo () -> size, hit, nclear ...
источник
Голосование за принятый ответ приводит к предположению, что то, что говорит @Triptych, верно для всех случаев. Это не обязательно правда. Одно большое отличие заключается в том, что вам нужно решить, принимать ли строку регулярного выражения или скомпилированный объект регулярного выражения в качестве параметра функции:
Всегда лучше скомпилировать свои регулярные выражения на случай, если вам потребуется их повторно использовать.
Обратите внимание, что в приведенном выше примере время имитирует создание скомпилированного объекта регулярного выражения один раз во время импорта, а не «на лету», когда это требуется для сопоставления.
источник
В качестве альтернативного ответа, поскольку я вижу, что он не упоминался ранее, я приведу цитаты из документов по Python 3 :
источник
Вот пример, где использование
re.compile
более чем в 50 раз быстрее, чем требуется .Дело в том же, что и в приведенном выше комментарии, а именно, использование
re.compile
может быть значительным преимуществом, если вы используете его так, что не получаете больших выгод от кэша компиляции. Это происходит по крайней мере в одном конкретном случае (с которым я столкнулся на практике), а именно, когда все следующее верно:re._MAXCACHE
, чей по умолчанию в настоящее время 512), иre._MAXCACHE
других регулярных выражений между ними, так что каждый из них выгружается из кэша между последовательными использованиями.Пример вывода, который я получаю на своем ноутбуке (Python 3.7.7):
Я не стал беспокоиться, так
timeit
как разница такая большая, но я получаю качественно похожие цифры каждый раз. Обратите внимание, что даже безre.compile
использования одного и того же регулярного выражения несколько раз и переходя к следующему не было так уж плохо (только примерно в 2 раза медленнее, чем сre.compile
), но в другом порядке (циклически повторяя много регулярных выражений) это значительно хуже , как и ожидалось. Кроме того , увеличение размера кэша тоже работает: просто установивre._MAXCACHE = len(patterns)
вsetup()
выше (конечно , я не рекомендую делать такие вещи , в производстве , как имена с подчеркиванием условно «частными») падает на ~ 23 секунды до ~ 0,7 секунды, что также соответствует нашему пониманию.источник
Регулярные выражения компилируются перед использованием при использовании второй версии. Если вы собираетесь выполнять его много раз, определенно лучше сначала скомпилировать его. Если не компилировать каждый раз, когда вы подходите для одного, это хорошо.
источник
Удобочитаемость / когнитивная нагрузка
Для меня главный выигрыш в том , что мне нужно только помнить, и читать, одну форму осложненного регулярных выражений синтаксис API - в
<compiled_pattern>.method(xxx)
форме , а не что и вre.func(<pattern>, xxx)
форме.Это
re.compile(<pattern>)
немного лишний шаблон, правда.Но в том, что касается регулярных выражений, этот дополнительный шаг компиляции вряд ли будет большой причиной когнитивной нагрузки. И на самом деле, на сложных шаблонах вы могли бы даже получить ясность, отделив объявление от любого метода регулярного выражения, который затем вызываете для него.
Я склонен сначала настраивать сложные шаблоны на таком веб-сайте, как Regex101, или даже в отдельном минимальном тестовом сценарии, а затем вносить их в мой код, поэтому отделение объявления от его использования также подходит моему рабочему процессу.
источник
Я хотел бы мотивировать, что предварительная компиляция является и концептуальной, и «буквально» (как в «грамотном программировании») выгодной. взгляните на этот фрагмент кода:
в своем заявлении вы бы написали:
это примерно настолько просто с точки зрения функциональности, насколько это возможно. потому что этот пример очень короткий, я нашел способ объединить
_text_has_foobar_re_search
все в одну строку. недостаток этого кода в том, что он занимает мало памяти для любого времени жизниTYPO
библиотечного объекта; Преимущество состоит в том, что при выполнении поиска в foobar вы получите два вызова функций и два поиска в словаре классов. сколько регулярных выражений кэшируется,re
и издержки этого кэша здесь не имеют значения.сравните это с более привычным стилем ниже:
В приложении:
Я с готовностью признаю, что мой стиль весьма необычен для питона, возможно, даже спорен. однако в примере, который более точно соответствует тому, как Python в основном используется, для того, чтобы выполнить одно совпадение, мы должны создать экземпляр объекта, выполнить три поиска в словаре экземпляра и выполнить три вызова функций; Кроме того, мы могли бы попасть в
re
столкнуться с проблемами кэширования при использовании более 100 регулярных выражений. Кроме того, регулярное выражение скрывается внутри тела метода, что в большинстве случаев не очень хорошая идея.Следует сказать, что каждый набор мер - целевые, псевдонимы импортных заявлений; методы с псевдонимами, где это применимо; сокращение вызовов функций и поиска в словаре объектов --- может помочь уменьшить вычислительную и концептуальную сложность.
источник
Насколько я понимаю, эти два примера фактически эквивалентны. Единственное отличие состоит в том, что в первом вы можете повторно использовать скомпилированное регулярное выражение в другом месте, не вызывая его повторную компиляцию.
Вот вам ссылка: http://diveintopython3.ep.io/refactoring.html
источник