В Python, как разбирать строку, представляющую набор аргументов ключевого слова, так что порядок не имеет значения

Я пишу класс RecurringInterval который – на основе объекта dateutil.rrule – представляет собой повторяющийся интервал времени. Я определил пользовательский, удобочитаемый для пользователя метод __str__ для него и хотел бы также определить метод parse который (подобно функции rrulestr () ) анализирует строку обратно в объект.

Ниже приведен метод parse и некоторые тестовые примеры:

  • регулярное выражение python для повторной строки
  • сортировать csv по столбцу
  • Регулярное выражение для возврата всех символов между двумя специальными символами
  • Регулярное выражение Python, сопоставление шаблона по нескольким строкам .. почему это не работает?
  • Написание анализатора для регулярных выражений
  • извлечение чисел из списка строк с помощью python
  •  import re from dateutil.rrule import FREQNAMES import pytest class RecurringInterval(object): freq_fmt = "{freq}" start_fmt = "from {start}" end_fmt = "till {end}" byweekday_fmt = "by weekday {byweekday}" bymonth_fmt = "by month {bymonth}" @classmethod def match_pattern(cls, string): SPACES = r'\s*' freq_names = [freq.lower() for freq in FREQNAMES] + [freq.title() for freq in FREQNAMES] # The frequencies may be either lowercase or start with a capital letter FREQ_PATTERN = '(?P<freq>{})?'.format("|".join(freq_names)) # Start and end are required (their regular expressions match 1 repetition) START_PATTERN = cls.start_fmt.format(start=SPACES + r'(?P<start>.+?)') END_PATTERN = cls.end_fmt.format(end=SPACES + r'(?P<end>.+?)') # The remaining tokens are optional (their regular expressions match 0 or 1 repetitions) BYWEEKDAY_PATTERN = cls.optional(cls.byweekday_fmt.format(byweekday=SPACES + r'(?P<byweekday>.+?)')) BYMONTH_PATTERN = cls.optional(cls.bymonth_fmt.format(bymonth=SPACES + r'(?P<bymonth>.+?)')) PATTERN = SPACES + FREQ_PATTERN \ + SPACES + START_PATTERN \ + SPACES + END_PATTERN \ + SPACES + BYWEEKDAY_PATTERN \ + SPACES + BYMONTH_PATTERN \ + SPACES + "$" # The character '$' is needed to make the non-greedy regular expressions parse till the end of the string return re.match(PATTERN, string).groupdict() @staticmethod def optional(pattern): '''Encloses the given regular expression in an optional group (ie, one that matches 0 or 1 repetitions of the original regular expression).''' return '({})?'.format(pattern) '''Tests''' def test_match_pattern_with_byweekday_and_bymonth(): string = "Weekly from 2017-11-03 15:00:00 till 2017-11-03 16:00:00 by weekday Monday, Tuesday by month January, February" groups = RecurringInterval.match_pattern(string) assert groups['freq'] == "Weekly" assert groups['start'].strip() == "2017-11-03 15:00:00" assert groups['end'].strip() == "2017-11-03 16:00:00" assert groups['byweekday'].strip() == "Monday, Tuesday" assert groups['bymonth'].strip() == "January, February" def test_match_pattern_with_bymonth_and_byweekday(): string = "Weekly from 2017-11-03 15:00:00 till 2017-11-03 16:00:00 by month January, February by weekday Monday, Tuesday " groups = RecurringInterval.match_pattern(string) assert groups['freq'] == "Weekly" assert groups['start'].strip() == "2017-11-03 15:00:00" assert groups['end'].strip() == "2017-11-03 16:00:00" assert groups['byweekday'].strip() == "Monday, Tuesday" assert groups['bymonth'].strip() == "January, February" if __name__ == "__main__": # pytest.main([__file__]) pytest.main([__file__+"::test_match_pattern_with_byweekday_and_bymonth"]) # This passes # pytest.main([__file__+"::test_match_pattern_with_bymonth_and_byweekday"]) # This fails 

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

    Каким образом можно заставить синтаксический анализатор анализировать «необязательные» поля в любом порядке, чтобы пройти оба теста? (Я думал о создании итератора со всеми перестановками регулярных выражений и попытке re.match на каждом из них, но это не похоже на элегантное решение).

  • Как правильно отсортировать строку с номером внутри?
  • извлечение чисел из списка строк с помощью python
  • разбор математического выражения в python и решение найти ответ
  • Заменить строки в файлах Python
  • Python: разделите строку по списку разделителей
  • Тип скомпилированного объекта регулярного выражения в python
  • 2 Solutions collect form web for “В Python, как разбирать строку, представляющую набор аргументов ключевого слова, так что порядок не имеет значения”

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

     from pyparsing import Regex, oneOf, OneOrMore # Boring old constants, I'm sure you know how to fill these out... months = ['January', 'February'] weekdays = ['Monday', 'Tuesday'] frequencies = ['Daily', 'Weekly'] # A datetime expression is anything matching this regex. We could split it down # even further to get day, month, year attributes in our results object if we felt # like it datetime_expr = Regex(r'(\d{4})-(\d\d?)-(\d\d?) (\d{2}):(\d{2}):(\d{2})') # A from or till expression is the word "from" or "till" followed by any valid datetime from_expr = 'from' + datetime_expr.setResultsName('from_') till_expr = 'till' + datetime_expr.setResultsName('till') # A range expression is a from expression followed by a till expression range_expr = from_expr + till_expr # A weekday is any old weekday weekday_expr = oneOf(weekdays) month_expr = oneOf(months) frequency_expr = oneOf(frequencies) # A by weekday expression is the words "by weekday" followed by one or more weekdays by_weekday_expr = 'by weekday' + OneOrMore(weekday_expr).setResultsName('weekdays') by_month_expr = 'by month' + OneOrMore(month_expr).setResultsName('months') # A recurring interval, then, is a frequency, followed by a range, followed by # a weekday and a month, in any order recurring_interval = frequency_expr + range_expr + (by_weekday_expr & by_month_expr) # Let's parse! if __name__ == '__main__': res = recurring_interval.parseString('Daily from 1111-11-11 11:00:00 till 1111-11-11 12:00:00 by weekday Monday by month January February') # Note that setResultsName causes everything to get packed neatly into # attributes for us, so we can pluck all the bits and pieces out with no # difficulty at all print res print res.from_ print res.till print res.weekdays print res.months 

    Здесь у вас много вариантов, каждый с разными минусами.

    Один из подходов заключался бы в использовании повторного чередования, например (by weekday|by month)* :

     (?P<freq>Weekly)?\s+from (?P<start>.+?)\s+till (?P<end>.+?)(?:\s+by weekday (?P<byweekday>.+?)|\s+by month (?P<bymonth>.+?))*$ 

    Это будет соответствовать строкам формы week month и month week , но также week week или month week month и т. Д.

    Другим вариантом будет использование lookaheads, например (?=.*by weekday)?(?=.*by month)? :

      (?P<freq>Weekly)?\s+from (?P<start>.+?)\s+till (?P<end>.+?(?=$| by))(?=.*\s+by weekday (?P<byweekday>.+?(?=$| by))|)(?=.*\s+by month (?P<month>.+?(?=$| by))|) 

    Однако для этого требуется известный разделитель (я использовал «by»), чтобы узнать, как далеко его можно сопоставить. Кроме того, он будет молча игнорировать любые дополнительные символы (это означает, что он будет соответствовать строкам формы by weekday [some gargabe] by month ).

    Python - лучший язык программирования в мире.