Жадные выражения в Pyparsing

Я пытаюсь разбить строку типа aaa:bbb(123) на токены с помощью Pyparsing.

Я могу сделать это с помощью регулярного выражения, но мне нужно сделать это с помощью Pyparsing.

С re решение будет выглядеть так:

>>> import re
>>> string = 'aaa:bbb(123)'
>>> regex = '(\S+):(\S+)\((\d+)\)'
>>> re.match(regex, string).groups()
('aaa', 'bbb', '123')

Это достаточно ясно и просто. Ключевым моментом здесь является \S+, что означает «все, кроме пробелов».

Теперь я попробую сделать это с помощью Pyparsing:

>>> from pyparsing import Word, Suppress, nums, printables
>>> expr = (
...     Word(printables, excludeChars=':')
...     + Suppress(':')
...     + Word(printables, excludeChars='(')
...     + Suppress('(')
...     + Word(nums)
...     + Suppress(')')
... )
>>> expr.parseString(string).asList()
['aaa', 'bbb', '123']

Хорошо, у нас тот же результат, но это выглядит не очень хорошо. Мы установили excludeChars, чтобы выражения Pyparsing останавливались там, где нам нужно, но это не выглядит надежным. Если у нас будут «исключенные» символы в исходной строке, то же регулярное выражение будет работать нормально:

>>> string = 'a:aa:b(bb(123)'
>>> re.match(regex, string).groups()
('a:aa', 'b(bb', '123')

в то время как исключение Pyparsing, очевидно, сломается:

>>> expr.parseString(string).asList()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/long/path/to/pyparsing.py", line 1111, in parseString
    raise exc
ParseException: Expected W:(0123...) (at char 7), (line:1, col:8)

Итак, вопрос в том, как мы можем реализовать необходимую логику с помощью Pyparsing?


person oblalex    schedule 22.11.2014    source источник


Ответы (2)


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

Если вы хотите, чтобы регулярное выражение смотрело вперед и назад, вы можете просто использовать регулярное выражение, содержащее исходное re:

expr = Regex(r"(\S+):(\S+)\((\d+)\)")
print expr.parseString(string).dump()

['aaa:b(bb(123)']

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

expr = Regex(r"(?P<field1>\S+):(?P<field2>\S+)\((?P<field3>\d+)\)")
print expr.parseString(string).dump()

['aaa:b(bb(123)']
- field1: aaa
- field2: b(bb
- field3: 123    

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

person PaulMcG    schedule 23.11.2014

Используйте регулярное выражение с предварительным утверждением:

from pyparsing import Word, Suppress, Regex, nums, printables

expr = (
     Word(printables, excludeChars=':')
     + Suppress(':')
     + Regex(r'\S+[^\(](?=\()')
     + Suppress('(')
     + Word(nums)
     + Suppress(')')
 )
person blaze    schedule 23.11.2014