Стоит ли использовать Python re.compile? - PullRequest
391 голосов
/ 17 января 2009

Есть ли преимущество в использовании компиляции для регулярных выражений в Python?

h = re.compile('hello')
h.match('hello world')

против

re.match('hello', 'hello world')

Ответы [ 24 ]

3 голосов
/ 18 января 2010

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

Я украл и убрал пример из «Мастеринг регулярных выражений» Джеффа Фридла. Это на 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 = 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
3 голосов
/ 20 января 2009

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

3 голосов
/ 11 января 2018

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

Использование compile помогает мне различать понятия
1. модуль (повторно)
2. объект регулярного выражения
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',]
}
3 голосов
/ 12 сентября 2016

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

>>> 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
3 голосов
/ 11 июля 2013

Помимо различий в производительности, использование 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 помог мне подтвердить мое предположение.

2 голосов
/ 14 марта 2017

Я действительно уважаю все вышеприведенные ответы. От моего мнения Да! Наверняка стоит использовать re.compile вместо компиляции регулярных выражений, снова и снова, каждый раз.

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

  1. Усилия процессора
  2. Сложность времени.
  3. Делает регулярное выражение универсальным. (Может использоваться в findall, search, match)
  4. И делает вашу программу классной.

Пример:

  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)

Точно так же вы можете использовать его для: Совпадения и замены

2 голосов
/ 17 января 2009

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

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

2 голосов
/ 21 марта 2019

Согласно документации Python :

Последовательность

prog = re.compile(pattern)
result = prog.match(string)

эквивалентно

result = re.match(pattern, string)

, но использование re.compile() и сохранение полученного объекта регулярного выражения для повторного использования более эффективно, когда выражение будет использоваться несколько раз в одной программе.

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

1 голос
/ 04 января 2017

У меня большой опыт работы с скомпилированным регулярным выражением 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

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

Обратите внимание, что в приведенном выше примере время имитирует создание скомпилированного объекта регулярного выражения один раз во время импорта по сравнению с «на лету», когда это требуется для сопоставления.

1 голос
/ 06 июля 2009

(месяцы спустя) легко добавить свой собственный кеш вокруг 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 ...

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...