Стоит ли использовать Python re.compile?

Значение a будет b, но значением выражение будет c. То есть в

d = (a = b, c);

a будет равно b, а d будет равно c.

406
задан Chad Birch 3 April 2009 в 21:44
поделиться

11 ответов

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

РЕДАКТИРОВАНИЕ: После быстрого взгляда на фактический код библиотеки Python 2.5 я вижу, что Python внутренне компилирует И КЭШИ regexes каждый раз, когда Вы используете их так или иначе (включая вызовы к re.match()), таким образом, Вы действительно только изменяетесь, КОГДА regex компилируется и не должен экономить много времени вообще - только время, это берет для проверки кэша (ключевой поиск на внутреннем 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

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

394
ответ дан Daniel Standage 3 April 2009 в 21:44
поделиться
  • 1
    Я знаю it' s несколько лет, но я для можно было бы хотеть знать, как точно Вы пошли об использовании этого, Cheeso. Вы переопределяете OnAfterInstall? Если так, как Вы вставляете Сценарий там? – jp2code 27 July 2011 в 15:32

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

Вот ссылка для Вас: http://diveintopython3.ep.io/refactoring.html

Вызывание поисковой функции объекта скомпилированного шаблона со строкой 'M' выполняет то же самое как звонящий re.search и с регулярным выражением и со строкой 'M'. Только очень, намного быстрее. (На самом деле функция re.search просто компилирует регулярное выражение и называет получающийся метод поиска объекта шаблона для Вас.)

-5
ответ дан Bill the Lizard 3 April 2009 в 21:44
поделиться

Регулярные выражения компилируются прежде чем быть используемым при использовании второй версии. Если Вы идете в выполнение его много раз, это - definatly лучше для компиляции его сначала. Не компилируя каждый раз Вы соответствуете для одного off's, прекрасен.

0
ответ дан Adam Peck 3 April 2009 в 21:44
поделиться

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

так, если Вы собираетесь быть использованием тот же regex много, это может стоить того, чтобы сделать re.compile (специально для более сложного regexes).

стандартные аргументы против преждевременной оптимизации применяются, но я не думаю, что Вы действительно теряете много ясности/прямоты при помощи re.compile, если Вы подозреваете, что Ваш regexps может стать узким местом производительности.

Обновление:

В соответствии с 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(...), т.е. никакого закулисного кэширования скомпилированного представления, кажется, не происходит.

55
ответ дан Ken Williams 3 April 2009 в 21:44
поделиться
  • 1
    фиксированный.. потому что я didn' t имеют относительный набор как положение – mark smith 2 June 2009 в 11:33

Интересно, компиляция действительно оказывается более эффективной для меня (Python 2.5.2 на 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 строки, прокомментированные наоборот, скомпилированный regex вдвое более быстр

3
ответ дан Eli Bendersky 3 April 2009 в 21:44
поделиться
  • 1
    @camickr: Я обращался к случаю, где JList начинается пустой и добавил объекты к нему в течение времени жизни приложения. Я все еще предпочитаю JTable, поскольку он также предлагает сортировку / фильтрующий из поля. Для калибровки столбцов таблицы, я обычно использую служебный метод, подобный: exampledepot.com/egs/javax.swing.table/PackCol.html – Adamski 16 November 2009 в 16:34

Для меня самое большое преимущество для re.compile является способностью разделить определение regex от его использования.

Даже простое выражение такой как 0|[1-9][0-9]* (целое число в основе 10 без начальных нулей) может быть достаточно сложным, который Вы не должны перепечатывать его, проверить, сделали ли Вы какие-либо опечатки, и позже должны перепроверить, существуют ли опечатки, когда Вы начинаете отлаживать. Плюс, более хорошо использовать имя переменной, такое как цифра или 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)

, Хотя это довольно близко, последняя строка вторых чувств, более естественных и более простых, когда используется неоднократно.

119
ответ дан Acumenus 3 April 2009 в 21:44
поделиться
  • 1
    Можно также объединить это в цепочку путем добавления множественных вызовов для анимации () .animate () друг после друга. – Kieran Andrews 17 May 2011 в 08:17

Это - хороший вопрос. Вы часто видите, что люди используют re.compile без причины. Это уменьшает удобочитаемость. Но уверенный существует много времен, когда предварительная компиляция выражения требуется. Как то, когда Вы используете повторенные времена в цикле или некоторых такой.

Это похоже на все о программировании (все в жизни на самом деле). Примените здравый смысл.

2
ответ дан PEZ 3 April 2009 в 21:44
поделиться
  • 1
    Любое время кто-то использует GridBagLayout, ангел, теряет его крылья. Лучше: определите свои потребности layouting и запишите Вашим customed менеджерам по расположению (например, для разметок ключа/значения). – Peter Walser 26 February 2010 в 12:46

В целом я нахожу, что легче использовать флаги (по крайней мере, легче помнить как), как re.I при компиляции шаблонов, чем использовать флаги встраивают.

>>> foo_pat = re.compile('foo',re.I)
>>> foo_pat.findall('some string FoO bar')
['FoO']

по сравнению с

>>> re.findall('(?i)foo','some string FoO bar')
['FoO']
5
ответ дан pradyunsg 4 April 2009 в 08:44
поделиться
  • 1
    +1 Хорошая подсказка с утверждением. Ошибки, которые происходят при доступе к UI снаружи EDT ofttimes тонкий и странный. И очень трудно найти. – Joey 16 November 2009 в 13:22

(месяцы спустя) легко добавить свой собственный кеш вокруг re.match, или что-нибудь еще в этом отношении -

""" Re.py: Re.match = re.match + cache  
    efficiency: re.py does this already (but what's _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 ...

Вибни, было бы неплохо, если бы: cachehint (size =), cacheinfo () -> size, hits, nclear ...

1
ответ дан 22 November 2019 в 23:24
поделиться

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

Я украл и убрал пример из "Овладения регулярными выражениями" Джеффа Фридла. Это на Macbook под управлением OSX 10.6 (2 ГГц intel core 2 duo, 4 ГБ барабан). Версия на питоне 2.6.1.

Run 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

Run 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
3
ответ дан 22 November 2019 в 23:24
поделиться

Я просто пробовал это сам. В простом случае выделения числа из строки и его суммирования использование скомпилированного объекта регулярного выражения примерно в два раза быстрее, чем использование методов 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 раз, а не один миллион.

16
ответ дан 22 November 2019 в 23:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: