Я использую регулярные выражения Python преступно неэффективно - PullRequest
7 голосов
/ 29 сентября 2008

Моя цель здесь - создать очень простой язык шаблонов. В данный момент я работаю над заменой переменной значением, например так:

Этот вход:

Паутина

Должен выдать этот вывод:

Сеть Это тестовая переменная

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

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

def stripMatchedQuotes(item):
    MatchedSingleQuotes = re.compile(r"'(.*)'", re.LOCALE)
    MatchedDoubleQuotes = re.compile(r'"(.*)"', re.LOCALE)
    item = MatchedSingleQuotes.sub(r'\1', item, 1)
    item = MatchedDoubleQuotes.sub(r'\1', item, 1)
    return item




def processVariables(item):
    VariableDefinition = re.compile(r'<%(.*?)=(.*?)%>', re.LOCALE)
    VariableUse = re.compile(r'<%(.*?)%>', re.LOCALE)
    Variables={}

    while VariableDefinition.search(item):
        VarName, VarDef = VariableDefinition.search(item).groups()
        VarName = stripMatchedQuotes(VarName).upper().strip()
        VarDef = stripMatchedQuotes(VarDef.strip())
        Variables[VarName] = VarDef
        item = VariableDefinition.sub('', item, 1)

    while VariableUse.search(item):
        VarName = stripMatchedQuotes(VariableUse.search(item).group(1).upper()).strip()
        item = VariableUse.sub(Variables[VarName], item, 1)

    return item

Ответы [ 10 ]

10 голосов
/ 29 сентября 2008

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

Другая возможность - использовать одно регулярное выражение, как показано ниже:

MatchedQuotes = re.compile(r"(['\"])(.*)\1", re.LOCALE)
item = MatchedQuotes.sub(r'\2', item, 1)

Наконец, вы можете объединить это в регулярное выражение в processVariables. Приняв предложение Торстена Марека использовать функцию для re.sub, это значительно улучшает и упрощает вещи.

VariableDefinition = re.compile(r'<%(["\']?)(.*?)\1=(["\']?)(.*?)\3%>', re.LOCALE)
VarRepl = re.compile(r'<%(["\']?)(.*?)\1%>', re.LOCALE)

def processVariables(item):
    vars = {}
    def findVars(m):
        vars[m.group(2).upper()] = m.group(4)
        return ""

    item = VariableDefinition.sub(findVars, item)
    return VarRepl.sub(lambda m: vars[m.group(2).upper()], item)

print processVariables('<%"TITLE"="This Is A Test Variable"%>The Web <%"TITLE"%>')

Вот мои сроки для 100000 пробежек:

Original       : 13.637
Global regexes : 12.771
Single regex   :  9.095
Final version  :  1.846

[Редактировать] Добавить отсутствующий не жадный спецификатор

[Edit2] Добавлены вызовы .upper (), поэтому без учета регистра, как в оригинальной версии

3 голосов
/ 29 сентября 2008

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

>>> import re
>>> var_matcher = re.compile(r'<%(.*?)%>', re.LOCALE)
>>> string = '<%"TITLE"%> <%"SHMITLE"%>'
>>> values = {'"TITLE"': "I am a title.", '"SHMITLE"': "And I am a shmitle."}
>>> var_matcher.sub(lambda m: vars[m.group(1)], string)
'I am a title. And I am a shmitle.

Следуйте советам eduffy.myopenid.com и сохраняйте скомпилированные регулярные выражения.

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

2 голосов
/ 29 сентября 2008

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

Как сказал Джейми Завински:

Некоторые люди, когда сталкиваются с проблема, подумай "Я знаю, я буду использовать регулярные выражения! "Теперь у них есть две проблемы.

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

2 голосов
/ 29 сентября 2008

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

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

1 голос
/ 29 сентября 2008

Почему бы не использовать Мако ? Шутки в сторону. Какая функция вам нужна, чтобы у Мако не было? Возможно, вы сможете адаптировать или расширить то, что уже работает.

1 голос
/ 29 сентября 2008

Если регулярное выражение содержит только один. * Подстановочный знак и литералы, то вы можете использовать find и rfind, чтобы найти открывающий и закрывающий разделители.

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

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

Кроме того, мне кажется, что это LL-разборный язык . Вы можете найти библиотеку, которая уже может анализировать такие вещи для вас. Вы также можете использовать рекурсивные вызовы для выполнения однопроходного анализа - например, вы можете реализовать функцию processVariables, чтобы использовать только первую кавычку, а затем вызвать функцию сопоставления кавычек, чтобы использовать ее до следующей кавычки и т. Д.

1 голос
/ 29 сентября 2008

Вы звоните re.compile совсем немного. Глобальная переменная для них не помешает.

1 голос
/ 29 сентября 2008

Вы можете сопоставить оба вида кавычек за один раз с r"(\"|')(.*?)\1" - \1 относится к первой группе, поэтому она будет соответствовать только совпадающим кавычкам.

0 голосов
/ 29 сентября 2008

Почему бы не использовать XML и XSLT вместо создания собственного языка шаблонов? То, что вы хотите сделать, довольно просто в XSLT.

0 голосов
/ 29 сентября 2008

Не вызывать поиск дважды подряд (в цикле условно и в первом операторе цикла). Вызов (и кеширование результата) один раз перед циклом, а затем в последнем выражении цикла.

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