Я использую регулярные выражения Python преступно неэффективным образом

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

Этот ввод:

The Web

Должен произвести такой вывод:

The Web This Is A Test Variable

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

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

def stripMatchedQuotes(item):
    MatchedSingleQuotes = re.compile(r"'(.*)'", re.LOCALE)
    MatchedDoubleQuotes = re.compile(r'"(.*)"', re.LOCALE)
    item = MatchedSingleQuotes.sub(r'', item, 1)
    item = MatchedDoubleQuotes.sub(r'', 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

Миру не нужен другой язык шаблонов Python. Их уже десятки. Разве ты не можешь просто использовать один из них?

John Millikin 29.09.2008 00:12

@Schof: позвольте мне указать вам на genshi.edgewall.org, который сделает вашу жизнь пирогом.

user7116 29.09.2008 01:28

Мне нравится Title +1 за то, что заставляет меня смеяться.

UnkwnTech 07.10.2008 09:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
3
2 109
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

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

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

Создание предметно-ориентированного языка - это совершенно нормально, если это уместно. Никогда не говори никогда.

skaffman 29.09.2008 00:23

Может быть, но уже есть миллион хороших вариантов для языков шаблонов текста в Python ...

Dan Lenski 29.09.2008 00:27

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

Schof 29.09.2008 00:31

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

JesperE 29.09.2008 19:22

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

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

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

Ответ принят как подходящий

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

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

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

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

VariableDefinition = re.compile(r'<%(["\']?)(.*?)=(["\']?)(.*?)%>', re.LOCALE)
VarRepl = re.compile(r'<%(["\']?)(.*?)%>', 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

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

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

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 и храните скомпилированные регулярные выражения.

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

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

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

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

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

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

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

Some people, when confronted with a problem, think "I know, I'll use regular expressions!" Now they have two problems.

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

В принципе согласен. Мы должны пойти в uservoice и заставить их показывать эту цитату каждый раз, когда кто-то пишет вопрос и присваивает ему тег «regex».

Torsten Marek 29.09.2008 01:25

Мне любопытно, почему вы думаете, что в этом примере нет ни удобочитаемости, ни эффективного синтаксического анализа? Что я могу изменить, чтобы сделать его более читаемым и легким для анализа?

Schof 30.09.2008 08:31

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

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

Другие вопросы по теме