вывод вложенного словаря из pyparsing

Я использую pyparsing для анализа выражения вида:

"and(or(eq(x,1), eq(x,2)), eq(y,3))"

Мой тестовый код выглядит так:

from pyparsing import Word, alphanums, Literal, Forward, Suppress, ZeroOrMore, CaselessLiteral, Group

field = Word(alphanums)
value = Word(alphanums)
eq_ = CaselessLiteral('eq') + Group(Suppress('(') + field + Literal(',').suppress() + value + Suppress(')'))
ne_ = CaselessLiteral('ne') + Group(Suppress('(') + field + Literal(',').suppress() + value + Suppress(')'))
function = ( eq_ | ne_ )

arg = Forward()
and_ = Forward()
or_ = Forward()

arg << (and_ | or_ |  function) + Suppress(",") + (and_ | or_ | function) + ZeroOrMore(Suppress(",") + (and_ | function))

and_ << Literal("and") + Suppress("(") + Group(arg) + Suppress(")")
or_ << Literal("or") + Suppress("(") + Group(arg) + Suppress(")")

exp = (and_ | or_ | function)

print(exp.parseString("and(or(eq(x,1), eq(x,2)), eq(y,3))"))

У меня есть вывод в форме:

['and', ['or', ['eq', ['x', '1'], 'eq', ['x', '2']], 'eq', ['y', '3']]]

Вывод списка выглядит нормально. Но для последующей обработки я хотел бы иметь вывод в виде вложенного словаря:

{
    name: 'and',
    args: [
        {
            name: 'or',
            args: [
                {
                    name: 'eq',
                    args: ['x','1']
                },
                {
                    name: 'eq',
                    args: ['x','2']
                }
            ]
        },
        {
            name: 'eq',
            args: ['y','3']
        }
    ]
}

Я пробовал класс Dict, но безуспешно.

Можно ли это сделать в pyparsing? Или я должен вручную отформатировать вывод списка?


person Horned Owl    schedule 11.08.2014    source источник


Ответы (2)


Функция, которую вы ищете, является важной в pyparsing, это установка имен результатов. Использование имен результатов рекомендуется для большинства приложений pyparsing. Эта функция существует с версии 0.9, т.к.

expr.setResultsName("abc")

Это позволяет мне получить доступ к этому конкретному полю общих проанализированных результатов как res["abc"] или res.abc (где res — это значение, возвращаемое из parser.parseString). Вы также можете вызвать res.dump(), чтобы увидеть вложенное представление ваших результатов.

Но все же помня о том, что синтаксические анализаторы легко понять с первого взгляда, я добавил поддержку этой формы setResultsName еще в версии 1.4.6:

expr("abc")

Вот ваш синтаксический анализатор с небольшой очисткой и добавленными именами результатов:

COMMA,LPAR,RPAR = map(Suppress,",()")
field = Word(alphanums)
value = Word(alphanums)
eq_ = CaselessLiteral('eq')("name") + Group(LPAR + field + COMMA + value + RPAR)("args")
ne_ = CaselessLiteral('ne')("name") + Group(LPAR + field + COMMA + value + RPAR)("args")
function = ( eq_ | ne_ )

arg = Forward()
and_ = Forward()
or_ = Forward()
exp = Group(and_ | or_ | function)

arg << delimitedList(exp)

and_ << Literal("and")("name") + LPAR + Group(arg)("args") + RPAR
or_ << Literal("or")("name") + LPAR + Group(arg)("args") + RPAR

К сожалению, dump() обрабатывает только вложенные результаты, а не списки значений, поэтому он не так удобен, как json.dumps (может быть, это было бы хорошим улучшением дампа?). Итак, вот пользовательский метод для вывода ваших вложенных результатов name-args:

ob = exp.parseString("and(or(eq(x,1), eq(x,2)), eq(y,3))")[0]

INDENT_SPACES = '    '
def dumpExpr(ob, level=0):
    indent = level * INDENT_SPACES
    print (indent + '{')
    print ("%s%s: %r," % (indent+INDENT_SPACES, 'name', ob['name']))
    if ob.name in ('eq','ne'):
        print ("%s%s: %s"   % (indent+INDENT_SPACES, 'args', ob.args.asList()))
    else:
        print ("%s%s: ["   % (indent+INDENT_SPACES, 'args'))
        for arg in ob.args:
            dumpExpr(arg, level+2)
        print ("%s]"   % (indent+INDENT_SPACES))
    print (indent + '}' + (',' if level > 0 else ''))
dumpExpr(ob)

Предоставление:

{
    name: 'and',
    args: [
        {
            name: 'or',
            args: [
                {
                    name: 'eq',
                    args: ['x', '1']
                },
                {
                    name: 'eq',
                    args: ['x', '2']
                },
            ]
        },
        {
            name: 'eq',
            args: ['y', '3']
        },
    ]
}
person PaulMcG    schedule 11.08.2014
comment
Да, это именно то, что мне нужно. И спасибо за очистку моего кода. Я новичок в pyparsing. - person Horned Owl; 11.08.2014
comment
Ну, иногда бывает зуд, надо просто почесать. Основываясь на работе над этим вопросом, я расширил метод dump() в списке классов ParseResults pyparsing из значений массива безымянных вложенных результатов. Он находится в последнем коде, зарегистрированном в SVN, и будет в версии 2.0.3. - person PaulMcG; 12.08.2014

Я не думаю, что pyparsing имеет что-то подобное, но вы можете рекурсивно создавать структуры данных:

def toDict(lst):
    if not isinstance(lst[1], list):
        return lst
    return [{'name': name, 'args': toDict(args)}
            for name, args in zip(lst[::2], lst[1::2])]

Ваш пример ведет себя по-разному на количестве args детей. Если это только один, вы просто используете dict, в противном случае это список диктов. Это приведет к сложному использованию. Лучше использовать список диктов, даже если есть один дочерний элемент. Таким образом, вы всегда знаете, как перебирать дочерние элементы без проверки типов.

Пример

Мы можем использовать json.dumps для красивого вывода (обратите внимание что здесь мы печатаем parsedict[0], потому что мы знаем, что у корня есть один дочерний элемент, но мы всегда возвращаем списки, как указано ранее):

import json
parsed = ['and', ['or', ['eq', ['x', '1'], 'eq', ['x', '2']], 'eq', ['y', '3']]]
parsedict = toDict(parsed)
print json.dumps(parsedict[0], indent=4, separators=(',', ': '))

Вывод

{
    "name": "and",
    "args": [
        {
            "name": "or",
            "args": [
                {
                    "name": "eq",
                    "args": [
                        "x",
                        "1"
                    ]
                },
                {
                    "name": "eq",
                    "args": [
                        "x",
                        "2"
                    ]
                }
            ]
        },
        {
            "name": "eq",
            "args": [
                "y",
                "3"
            ]
        }
    ]
}

Чтобы получить этот вывод, я заменил dict на collections.OrderedDict в функции toDict, просто чтобы name стоял перед args.

person enrico.bacis    schedule 11.08.2014
comment
вывод: '{'args': [{'args': [{'args': [['x', '1'], 'eq', ['x', '2']], 'name': 'eq'}, 'eq', ['y', '3']], 'name': 'or'}], 'name': 'and'} ' Структура для ['x', '1'] и ['x', '2'] неверно. - person Stephen Lin; 11.08.2014
comment
Хорошая идея - нередко pyparsing просто выполняет структурирование проанализированных данных в иерархию, а затем выполняет переход преобразования в вашу конкретную структуру данных, если имена результатов и действия синтаксического анализа неадекватны (например, когда желаемый структура выходных данных представляет собой некоторое обобщение или накопление данных, что затруднительно для проанализированных подэлементов). В этом случае достаточно имен результатов и добавления групп для структуры. - person PaulMcG; 11.08.2014