Есть ли какая-либо польза от использования компиляции для регулярных выражений в Python?
h = re.compile('hello')
h.match('hello world')
против
re.match('hello', 'hello world')
Есть ли какая-либо польза от использования компиляции для регулярных выражений в Python?
h = re.compile('hello')
h.match('hello world')
против
re.match('hello', 'hello world')
У меня был большой опыт работы с скомпилированным регулярным выражением 1000 раз по сравнению с компиляцией "на лету" и не заметил никакой заметной разницы. Очевидно, что это анекдотично и, конечно же, не является большим аргументом против компиляции, но я обнаружил, что разница незначительна.
EDIT:
После быстрого взгляда на фактический код библиотеки Python 2.5, я вижу, что Python внутренне компилирует регулярные выражения AND CACHES всякий раз, когда вы их используете (в том числе вызовы на re.match()
), поэтому вы действительно изменяете WHEN, когда регулярное выражение компилируется и должно не нужно экономить много времени - только время, необходимое для проверки кеша (ключевой поиск по внутреннему типу 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 is not None: return p
# ...
# Does actual compilation on cache miss
# ...
# Caches compiled regex
if len(_cache) >= _MAXCACHE:
_cache.clear()
_cache[cachekey] = p
return p
Я до сих пор часто компилирую регулярные выражения, но только для привязки их к хорошему многократно используемому имени, а не к ожидаемому увеличению производительности.
Для меня самым большим преимуществом 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)
Хотя это довольно близко, последняя строка второй кажется более естественной и простой при повторном использовании.
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
(особенно для более сложных регулярных выражений).
Применяются стандартные аргументы против преждевременной оптимизации, но я не думаю, что вы действительно потеряете много ясности/прямолинейности при использовании 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
Python 3.6.5 :: Anaconda, Inc.
Я также добавил случай (обратите внимание на различия в кавычках между двумя последними прогонами), который показывает, что re.match(x,...)
буквально [приблизительно] эквивалентно re.compile(x).match(...)
то есть никакого закулисного кеширования скомпилированного представления, похоже, не происходит.
Вот простой тестовый пример:
~$ for x in 1 10 100 1000 10000 100000 1000000; 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 in 1 10 100 1000 10000 100000 1000000; 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
Итак, похоже, что компиляция выполняется быстрее с этим простым случаем, даже если вы только один раз сопоставляете.
Я просто попробовал это сам. Для простого случая разбора числа из строки и его суммирования использование скомпилированного объекта регулярного выражения примерно в два раза быстрее, чем использование методов re
.
Как уже отмечали другие, методы re
(включая re.compile
) ищут строку регулярного выражения в кэше ранее скомпилированных выражений. Следовательно, в обычном случае дополнительные затраты на использование методов re
- это просто стоимость поиска в кэше.
Тем не менее, изучение кода, показывает, что кэш ограничен до 100 выражений. Возникает вопрос: насколько больно переполнять кеш? Код содержит внутренний интерфейс для компилятора регулярных выражений re.sre_compile.compile
. Если мы это называем, мы обходим кеш. Оказывается, что оно примерно на два порядка медленнее для основного регулярного выражения, такого как r'\w+\s+([0-9_]+)\s+\w*'
.
Вот мой тест:
#!/usr/bin/env python
import 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"
@timed
def noncompiled():
a = 0
for x in xrange(1000000):
m = re.match(regularExpression, testString)
a += int(m.group(1))
return a
@timed
def compiled():
a = 0
rgx = re.compile(regularExpression)
for x in xrange(1000000):
m = rgx.match(testString)
a += int(m.group(1))
return a
@timed
def 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
@timed
def compiledInLoop():
a = 0
for x in xrange(1000000):
rgx = re.compile(regularExpression)
m = rgx.match(testString)
a += int(m.group(1))
return a
@timed
def reallyCompiledInLoop():
a = 0
for 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 раз, а не один миллион.
Я согласен с Honest Abe в том, что 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(...)
вызывается только один раз, нам нужно сохранить скомпилированный объект регулярного выражения в памяти. Если мы используем класс, мы можем сохранить объект и повторно использовать его при каждом вызове нашей функции.
class Foo:
regex = re.compile('hello')
def my_function(text)
return regex.match(text)
Если мы не используем класс (это мой запрос сегодня), то у меня нет комментариев. Я все еще участвую в использовании глобальной переменной в Python, и я знаю, что глобальная переменная - это плохо.
Еще один момент, я считаю, что использование подхода (A) + (B)
имеет верх. Вот некоторые факты, которые я наблюдал (пожалуйста, поправьте меня, если я ошибаюсь):
Вызов Один раз, он выполнит один поиск в _cache
, а затем один sre_compile.compile()
, чтобы создать объект регулярного выражения. Вызов дважды, он выполнит два поиска и один компилятор (потому что объект regex кэшируется).
Если _cache
закрашивается между ними, тогда объект regex освобождается из памяти, и Python необходимо снова скомпилировать. (кто-то подсказывает, что Python не будет перекомпилировать.)
Если мы сохраняем объект регулярного выражения с помощью (A), объект регулярного выражения все равно попадет в _cache и как-нибудь покраснет. Но наш код сохраняет ссылку на него, и объект regex не будет выпущен из памяти. Те, Python не нужно компилировать снова.
2-секундные различия в критериях Джорджа compiledInLoop против скомпилированного - это в основном время, необходимое для создания ключа и поиска в _cache. Это не означает время компиляции регулярного выражения.
Джордж действительно показывает, что произойдет, если он действительно повторяет компиляцию каждый раз: он будет на 100 раз медленнее (он сократил цикл с 1 000 000 до 10 000).
Вот только случаи, когда (A + B) лучше, чем (C):
Дело в том, что (C) достаточно хорошо:
Просто повторите, вот A B C:
h = re.compile('hello') # (A)
h.match('hello world') # (B)
re.match('hello', 'hello world') # (C)
Спасибо за чтение.
В основном, есть небольшая разница, используете ли вы re.compile или нет. Внутри все функции реализованы с точки зрения компиляции:
def match(pattern, string, flags=0):
return _compile(pattern, flags).match(string)
def fullmatch(pattern, string, flags=0):
return _compile(pattern, flags).fullmatch(string)
def search(pattern, string, flags=0):
return _compile(pattern, flags).search(string)
def sub(pattern, repl, string, count=0, flags=0):
return _compile(pattern, flags).sub(repl, string, count)
def subn(pattern, repl, string, count=0, flags=0):
return _compile(pattern, flags).subn(repl, string, count)
def split(pattern, string, maxsplit=0, flags=0):
return _compile(pattern, flags).split(string, maxsplit)
def findall(pattern, string, flags=0):
return _compile(pattern, flags).findall(string)
def finditer(pattern, string, flags=0):
return _compile(pattern, flags).finditer(string)
Кроме того, re.compile() обходит дополнительную логику адресации и кэширования:
_cache = {}
_pattern_type = type(sre_compile.compile("", 0))
_MAXCACHE = 512
def _compile(pattern, flags):
# internal: compile pattern
try:
p, loc = _cache[type(pattern), pattern, flags]
if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE):
return p
except KeyError:
pass
if isinstance(pattern, _pattern_type):
if flags:
raise ValueError(
"cannot process flags argument with a compiled pattern")
return pattern
if not sre_compile.isstring(pattern):
raise TypeError("first argument must be string or compiled pattern")
p = sre_compile.compile(pattern, flags)
if not (flags & DEBUG):
if len(_cache) >= _MAXCACHE:
_cache.clear()
if p.flags & LOCALE:
if not _locale:
return p
loc = _locale.setlocale(_locale.LC_CTYPE)
else:
loc = None
_cache[type(pattern), pattern, flags] = p, loc
return p
В дополнение к небольшому выигрышу от использования re.compile, людям также нравится читаемость, которая исходит из наименования потенциально сложных спецификаций шаблонов и отделяет их от бизнес-логики, в которой применяются:
#### Patterns ############################################################
number_pattern = re.compile(r'\d+(\.\d*)?') # Integer or decimal number
assign_pattern = re.compile(r':=') # Assignment operator
identifier_pattern = re.compile(r'[A-Za-z]+') # Identifiers
whitespace_pattern = re.compile(r'[\t ]+') # Spaces and tabs
#### Applications ########################################################
if whitespace_pattern.match(s): business_logic_rule_1()
if assign_pattern.match(s): business_logic_rule_2()
Обратите внимание: один другой респондент неправильно полагал, что файлы pyc хранят скомпилированные шаблоны напрямую; однако на самом деле они перестраиваются каждый раз при загрузке PYC:
>>> from dis import dis
>>> with open('tmp.pyc', 'rb') as f:
f.read(8)
dis(marshal.load(f))
1 0 LOAD_CONST 0 (-1)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (re)
9 STORE_NAME 0 (re)
3 12 LOAD_NAME 0 (re)
15 LOAD_ATTR 1 (compile)
18 LOAD_CONST 2 ('[aeiou]{2,5}')
21 CALL_FUNCTION 1
24 STORE_NAME 2 (lc_vowels)
27 LOAD_CONST 1 (None)
30 RETURN_VALUE
Вышеупомянутая разборка происходит из файла PYC для tmp.py
, содержащего:
import re
lc_vowels = re.compile(r'[aeiou]{2,5}')
В общем, я считаю, что проще использовать флаги (по крайней мере, проще запомнить), например re.I
при компиляции шаблонов, чем использовать встроенные флаги.
>>> foo_pat = re.compile('foo',re.I)
>>> foo_pat.findall('some string FoO bar')
['FoO']
vs
>>> re.findall('(?i)foo','some string FoO bar')
['FoO']
Используя приведенные примеры:
h = re.compile('hello')
h.match('hello world')
Метод совпадения в приведенном выше примере не совпадает с тем, который используется ниже:
re.match('hello', 'hello world')
re.compile() возвращает объект регулярного выражения, что означает, что h
является объектом регулярных выражений.
Объект regex имеет свой собственный метод match с необязательными параметрами pos и endpos:
regex.match(string[, pos[, endpos]])
поз
Необязательный второй параметр pos дает индекс в строке, где поиск начнется; он по умолчанию равен 0. Это не полностью эквивалентно разрезанию строки; символ шаблона
'^'
совпадает с реальное начало строки и позиции сразу после newline, но не обязательно в индексе, где поиск начать.
endpos
Необязательный параметр endpos ограничивает, насколько далеко будет строка поиск; это будет как если бы строка была символом endpos длинной, поэтому будут отображаться только символы из pos до
endpos - 1
. совпадение. Если endpos меньше, чем pos, совпадение не будет найдено; в противном случае, если rx - скомпилированный объект регулярного выражения,rx.search(string, 0, 50)
эквивалентенrx.search(string[:50], 0)
.
Режимы поиска объектов, findall и finditer также поддерживают эти параметры.
re.match(pattern, string, flags=0)
не поддерживает их, как вы можете видеть,
а также его поисковые, поисковые и находные копии.
A объект сопоставления содержит атрибуты, которые дополняют эти параметры:
match.pos
Значение pos, которое было передано методу search() или match() объект регулярного выражения. Это индекс в строку, в которой RE двигатель начал искать совпадение.
match.endpos
Значение endpos, которое было передано методу search() или match() объекта регулярного выражения. Это индекс в строку, за которой Двигатель RE не пойдет.
A объект regex имеет два уникальных, возможно полезных атрибута:
regex.groups
Число захваченных групп в шаблоне.
regex.groupindex
Словарь, отображающий любые имена символических групп, определенные (? P), на номера групп. Словарь пуст, если не было использовано никаких символических групп в шаблоне.
И, наконец, объект соответствия имеет этот атрибут:
match.re
Объект регулярного выражения, метод match() или search() создал этот экземпляр совпадения.
Существует один дополнительный способ использования re.compile() в виде добавления комментариев к моим шаблонам регулярных выражений с использованием re.VERBOSE
pattern = '''
hello[ ]world # Some info on my pattern logic. [ ] to recognize space
'''
re.search(pattern, 'hello world', re.VERBOSE)
Хотя это не влияет на скорость запуска вашего кода, мне нравится делать это таким образом, поскольку это часть моей привычки комментирования. Мне не нравится тратить время, пытаясь вспомнить логику, которая отстала от моего кода через 2 месяца после того, как я хочу внести изменения.
Интересно, что компиляция для меня оказывается более эффективной (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 += 1
print time.time() - t
Запуск вышеуказанного кода один раз, как есть, и один раз, когда две строки if
прокомментировали наоборот, скомпилированное регулярное выражение в два раза быстрее
Я провел этот тест, прежде чем споткнуться о дискуссии здесь. Однако, запустив его, я подумал, что, по крайней мере, опубликую свои результаты.
Я украл и укрепил пример в Джеффе Фридле "Освоение регулярных выражений". Это на macbook с OSX 10.6 (2Ghz intel core 2 duo, 4GB RAM). Версия 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 = 1000
TestString = ""
for i in range(1000):
TestString += "abababdedfg"
StartTime = time.time()
for i in range(TimesToDo):
Regex1.search(TestString)
Seconds = time.time() - StartTime
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"
StartTime = time.time()
for i in range(TimesToDo):
Regex2.search(TestString)
Seconds = time.time() - StartTime
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"
Alternation takes 2.299 seconds
Character Class takes 0.107 seconds
Выполнить 2 - Не использовать re.compile
import re
import time
import fpformat
TimesToDo = 1000
TestString = ""
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() - StartTime
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"
StartTime = time.time()
for i in range(TimesToDo):
re.search('^[a-g]+$',TestString)
Seconds = time.time() - StartTime
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"
Alternation takes 2.508 seconds
Character Class takes 0.109 seconds
Отличие от производительности, используя 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.match делает его еще медленнее, и передача re.match со строкой patter находится где-то посередине.
>>> ipr = r'\D+((([0-2][0-5]?[0-5]?)\.){3}([0-2][0-5]?[0-5]?))\D+'
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.5077415757028423
>>> ipr = re.compile(ipr)
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.8324008992184038
>>> average(*timeit.repeat("ipr.match('abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
0.9187896518778871
Помимо производительности.
Использование compile
помогает мне различать понятия
1. модуль (re),
2. regex object
3. совместить объект
Когда я начал изучать регулярное выражение
#regex object
regex_object = re.compile(r'[a-zA-Z]+')
#match object
match_object = regex_object.search('1.Hello')
#matching content
match_object.group()
output:
Out[60]: 'Hello'
V.S.
re.search(r'[a-zA-Z]+','1.Hello').group()
Out[61]: 'Hello'
В качестве дополнения я сделал исчерпывающую страницу для модуля 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 вместо компиляции регулярного выражения, снова и снова, каждый раз.
Использование re.compile делает ваш код более динамичным, так как вы можете вызывать уже скомпилированное регулярное выражение, а не компилировать снова и aagain. Эта вещь приносит вам пользу:
Пример:
example_string = "The room number of her room is 26A7B."
find_alpha_numeric_string = re.compile(r"\b\w+\b")
find_alpha_numeric_string.findall(example_string)
find_alpha_numeric_string.search(example_string)
Аналогично вы можете использовать его для: Match и Substitute
Согласно документации Python:
Последовательность
prog = re.compile(pattern)
result = prog.match(string)
эквивалентно
result = re.match(pattern, string)
но использование re.compile()
и сохранение полученного объекта регулярного выражения для повторного использования более эффективно, когда выражение будет использоваться несколько раз в одной программе.
Итак, мой вывод: если вы собираетесь сопоставить один и тот же шаблон для разных текстов, вам лучше его предварительно скомпилировать.
(через несколько месяцев) легко добавить свой собственный кеш в re.match, или что-нибудь еще в этом отношении -
""" Re.py: Re.match = re.match + cache
efficiency: re.py does this already (but what _MAXCACHE ?)
readability, inline / separate: matter of taste
"""
import re
cache = {}
_re_type = type( re.compile( "" ))
def match( pattern, str, *opt ):
""" Re.match = re.match + cache re.compile( pattern )
"""
if type(pattern) == _re_type:
cpat = pattern
elif pattern in cache:
cpat = cache[pattern]
else:
cpat = cache[pattern] = re.compile( pattern, *opt )
return cpat.match( str )
# def search ...
A wibni, было бы неплохо, если бы: cachehint (size =), cacheinfo() → размер, хиты, nclear...
У меня был большой опыт работы с скомпилированным регулярным выражением 1000s времени и компиляции на лету, и не заметили любая воспринимаемая разница
Голоса принятого ответа приводят к предположению, что то, что @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
Всегда лучше компилировать ваши регулярные выражения, если вам нужно их повторно использовать.
Обратите внимание, что пример в timeit выше имитирует создание скомпилированного объекта регулярного выражения один раз во время импорта по сравнению с "на лету", когда это необходимо для соответствия.
Регулярные выражения компилируются перед использованием при использовании второй версии. Если вы собираетесь выполнять его много раз, то, безусловно, лучше скомпилировать его в первую очередь. Если вы не компилируете каждый раз, когда вы соответствуете одному, это нормально.
Я бы хотел, чтобы предварительная компиляция была концептуально и "грамотно" (как в "грамотном программировании" ). посмотрите этот фрагмент кода:
from re import compile as _Re
class TYPO:
def text_has_foobar( self, text ):
return self._text_has_foobar_re_search( text ) is not None
_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 вы получите две функции и два словаря поиска. сколько regexes кэшируется re
, и накладные расходы этого кэша здесь неактуальны.
сравните это с более обычным стилем, ниже:
import re
class Typo:
def text_has_foobar( self, text ):
return re.compile( r"""(?i)foobar""" ).search( text ) is not None
В приложении:
typo = Typo()
print( typo.text_has_foobar( 'FOObar ) )
Я с готовностью признаю, что мой стиль необычен для python, может быть, даже спорный. однако в примере, который более точно соответствует тому, как используется python в основном, для того, чтобы выполнить одно совпадение, мы должны создать экземпляр объекта, выполнить три словарных поиска в словаре и выполнить три вызова функций; Кроме того, мы можем столкнуться с проблемами кэширования re
при использовании более 100 регулярных выражений. также регулярное выражение скрывается внутри тела метода, которое в большинстве случаев не является такой хорошей идеей.
будь то сказано, что каждое подмножество мер --- целевые, сглаженные импортные заявления; где это применимо; сокращение вызовов функций и поиск словаря объектов --- может помочь уменьшить вычислительную и концептуальную сложность.
Я понимаю, что эти два примера эффективно эквивалентны. Единственное различие заключается в том, что в первом случае вы можете повторно использовать скомпилированное регулярное выражение в другом месте, не заставляя его компилироваться снова.
Вот ссылка для вас: http://diveintopython3.ep.io/refactoring.html
Вызов функции поиска объекта скомпилированного шаблона со строкой "M" выполняет то же самое, что и вызов re.search как с регулярным выражением, так и с строкой "M". Только намного, намного быстрее. (Фактически функция re.search просто компилирует регулярное выражение и вызывает для вас результирующий метод поиска объекта шаблона.)