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

387

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

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

против

re.match('hello', 'hello world')
  • 7
    Кроме того факта, что в 2.6 re.sub не будет принимать аргумент flags ...
  • 44
    Я только что натолкнулся на случай, когда использование re.compile дало улучшение в 10-50 раз. Мораль состоит в том, что если у вас много регулярных выражений (более MAXCACHE = 100), и вы используете их много раз каждый (и разделены более чем регулярными выражениями MAXCACHE между ними, так что каждый из них сбрасывается из кэша: используйте то же самое много раз, а затем переход к следующему не считается), тогда это определенно поможет скомпилировать их. В противном случае это не имеет значения.
Показать ещё 6 комментариев
Теги:

22 ответа

388
Лучший ответ

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

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

  • 11
    Ваш вывод не соответствует вашему ответу. Если регулярные выражения компилируются и сохраняются автоматически, в большинстве случаев нет необходимости делать это вручную.
  • 74
    Дж. Ф. Себастьян, это служит сигналом для программиста, что рассматриваемое регулярное выражение будет много использоваться и не должно быть отбрасыванием.
Показать ещё 11 комментариев
103

Для меня наибольшее преимущество 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)

Хотя он довольно близок, последняя строка второй кажется более естественной и простой при повторном использовании.

  • 2
    Я согласен с этим ответом; часто использование re.compile приводит к большему, не менее читабельному коду.
  • 1
    Иногда, наоборот, верно - например, если вы определяете регулярное выражение в одном месте и используете соответствующие группы в другом отдаленном месте.
Показать ещё 2 комментария
53

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

  • 5
    Основные проблемы с вашей методологией здесь, поскольку аргумент настройки НЕ включается во время. Таким образом, вы удалили время компиляции из второго примера и просто усреднили его в первом примере. Это не значит, что первый пример компилируется каждый раз.
  • 1
    Да, я согласен, что это несправедливое сравнение двух случаев.
Показать ещё 7 комментариев
37

Вот простой тестовый пример:

~$ 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

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

  • 2
    Какая версия Python это?
  • 1
    это на самом деле не имеет значения, дело в том, чтобы попробовать эталон в среде, где вы будете запускать код
Показать ещё 3 комментария
14

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

10

Я согласен с 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):

  • Если мы можем кэшировать ссылку объекта regex внутри класса.
  • Если нам нужно многократно называть (B) (внутри цикла или несколько раз), мы должны кэшировать ссылку на объект регулярного выражения вне цикла.

Дело в том, что (C) достаточно хорошо:

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

Просто повторите, вот A B C:

h = re.compile('hello')                   # (A)
h.match('hello world')                    # (B)
re.match('hello', 'hello world')          # (C)

Спасибо за чтение.

7

В основном, есть небольшая разница, используете ли вы 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}')
  • 1
    такое " в def search(pattern, string, flags=0):" опечатка?
  • 1
    Обратите внимание, что если pattern уже является скомпилированным шаблоном, затраты на кэширование становятся значительными: хэширование SRE_Pattern дорого, и шаблон никогда не записывается в кэш, поэтому поиск каждый раз завершается с KeyError .
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']
4

Существует один дополнительный способ использования re.compile() в виде добавления комментариев к моим шаблонам регулярных выражений с использованием re.VERBOSE

pattern = '''
hello[ ]world    # Some info on my pattern logic. [ ] to recognize space
'''

re.search(pattern, 'hello world', re.VERBOSE)

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

  • 1
    Я отредактировал твой ответ. Я думаю, что упоминание re.VERBOSE имеет смысл, и оно добавляет то, что другие ответы, похоже, не re.VERBOSE . Однако если вы ответите «Я пишу здесь, потому что я пока не могу комментировать», то обязательно удалите его. Пожалуйста, не используйте поле для ответов ни для чего, кроме ответов. Вы только один или два хороших ответа от возможности комментировать в любом месте (50 повторений), поэтому, пожалуйста, будьте терпеливы. Помещая комментарии в поля для ответов, когда вы знаете, что не должны, вы быстрее туда не попадете. Это даст вам отрицательные отзывы и удаленные ответы.
  • 1
    спасибо за ваш совет =) я буду помнить это
4

Используя приведенные примеры:

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

3

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

Использование 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',]
}
3

Этот ответ может прибыть поздно, но интересная находка. Использование компиляции может сэкономить ваше время, если вы планируете многократно использовать регулярное выражение (это также упоминается в документах). Ниже вы можете видеть, что использование скомпилированного регулярного выражения является самым быстрым, когда метод сопоставления непосредственно вызывает его. передача скомпилированного регулярного выражения в 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
3

Отличие от производительности, используя 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 помочь мне утвердить мое предположение.

3

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

Я украл и укрепил пример в Джеффе Фридле "Освоение регулярных выражений". Это на 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
3

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

  • 2
    Та же проблема, что и при сравнении производительности dF. Это не совсем справедливо, если вы не включите стоимость производительности самого оператора компиляции.
  • 6
    Карл, я не согласен. Компиляция выполняется только один раз, а соответствующий цикл выполняется миллион раз
Показать ещё 3 комментария
2

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

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

  • Процессорные усилия
  • Сложность времени.
  • Делает регулярное выражение Universal (может использоваться в поиске, поиске, совпадении)
  • И делает вашу программу классной.

Пример:

  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)

Аналогично вы можете использовать его для: Match и Substitute

2

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

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

  • 0
    Насколько я могу судить по моему краткому обзору, Python в двух словах не упоминает об использовании без re.compile (), что меня заинтересовало.
  • 2
    -1 Как это снижает читабельность?
Показать ещё 1 комментарий
1

У меня был большой опыт работы с скомпилированным регулярным выражением 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 выше имитирует создание скомпилированного объекта регулярного выражения один раз во время импорта по сравнению с "на лету", когда это необходимо для соответствия.

1

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

0

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

-2

Я бы хотел, чтобы предварительная компиляция была концептуально и "грамотно" (как в "грамотном программировании" ). посмотрите этот фрагмент кода:

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

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

  • 2
    WTF. Не только вы откопали старый, отвеченный вопрос. Ваш код не является идиоматическим и неправильным на многих уровнях - (ab) использование классов в качестве пространств имен, где достаточно модуля, использование заглавных букв классов и т. Д. См. Pastebin.com/iTAXAWen для лучших реализаций. Не говоря уже о том, что используемое вами регулярное выражение также нарушено. Всего -1
  • 2
    виновным. это старый вопрос, но я не возражаю против того, чтобы быть # 100 в замедленном разговоре. вопрос не был закрыт. Я предупреждал, что мой код может быть противником некоторых вкусов. я думаю, если бы вы могли рассматривать это как простую демонстрацию того, что выполнимо в python, например: если мы берем все, все, во что мы верим, как необязательные, а затем объединяем все, что угодно, как выглядят вещи, которые мы можем получить? Я уверен, что вы можете различить достоинства и недостатки этого решения и можете жаловаться более четко. в противном случае я должен сделать вывод, что ваше утверждение о неправомерности опирается на чуть больше, чем PEP008
Показать ещё 3 комментария
-4

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

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

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

  • 1
    я не отрицал вас, но технически это неправильно: Python все равно не перекомпилируется

Ещё вопросы

Сообщество Overcoder
Наверх
Меню