如何使用pyparsing解析具有多个打开/关闭类型的嵌套表达式?

15
我想使用pyparsing来解析形如:expr = '(gimme [some {nested [lists]}])'的表达式,并返回一个Python列表:[[['gimme', ['some', ['nested', ['lists']]]]]]。目前我的语法如下:

nestedParens = nestedExpr('(', ')')
nestedBrackets = nestedExpr('[', ']')
nestedCurlies = nestedExpr('{', '}')
enclosed = nestedParens | nestedBrackets | nestedCurlies

现在,enclosed.searchString(expr)返回的列表形式为:[[['gimme', ['some', '{nested', '[lists]}']]]]。这不是我想要的,因为它没有识别方括号或大括号,但我不知道为什么。

2个回答

28
这里有一个使用自修改语法的pyparsing解决方案,动态匹配正确的闭合括号字符。
from pyparsing import *

data = '(gimme [some {nested, nested [lists]}])'

opening = oneOf("( { [")
nonBracePrintables = ''.join(c for c in printables if c not in '(){}[]')
closingFor = dict(zip("({[",")}]"))
closing = Forward()
# initialize closing with an expression
closing << NoMatch()
closingStack = []
def pushClosing(t):
    closingStack.append(closing.expr)
    closing << Literal( closingFor[t[0]] )
def popClosing():
    closing << closingStack.pop()
opening.setParseAction(pushClosing)
closing.setParseAction(popClosing)

matchedNesting = nestedExpr( opening, closing, Word(alphas) | Word(nonBracePrintables) )

print matchedNesting.parseString(data).asList()

打印:

[['gimme', ['some', ['nested', ',', 'nested', ['lists']]]]]

更新: 我发布了上面的解决方案,因为我实际上在一年前就写过这个实验。我仔细看了一下你的原始帖子,它让我想到了由operatorPrecedence方法创建的递归类型定义,所以我重新使用了你的原始方法来完成这个解决方案 - 更容易理解!(但是可能会在右输入数据上出现左递归问题,没有经过充分测试):

from pyparsing import *

enclosed = Forward()
nestedParens = nestedExpr('(', ')', content=enclosed) 
nestedBrackets = nestedExpr('[', ']', content=enclosed) 
nestedCurlies = nestedExpr('{', '}', content=enclosed) 
enclosed << (Word(alphas) | ',' | nestedParens | nestedBrackets | nestedCurlies)


data = '(gimme [some {nested, nested [lists]}])' 

print enclosed.parseString(data).asList()

提供:

[['gimme', ['some', ['nested', ',', 'nested', ['lists']]]]]

编辑: 这是更新后解析器的图示,使用的是pyparsing 3.0中即将推出的铁路图支持。 railroad diagram


Paul,非常感谢您提供如此有价值的答案。更重要的是,非常感谢您创造并开源了我的新最爱Python库! pyparsing大大帮助我减少了我正在处理的一个项目的大小,复杂性和可维护性。 - Derek
1
如果有人对更新后的示例中使用的<<运算符感到困惑,请参阅pyparsing Forward类的文档:https://pythonhosted.org/pyparsing/pyparsing.Forward-class.html - skelliam

-3
这对你来说应该管用。我在你的示例上进行了测试:
import re
import ast

def parse(s):
    s = re.sub("[\{\(\[]", '[', s)
    s = re.sub("[\}\)\]]", ']', s)
    answer = ''
    for i,char in enumerate(s):
        if char == '[':
            answer += char + "'"
        elif char == '[':
            answer += "'" + char + "'"
        elif char == ']':
            answer += char
        else:
            answer += char
            if s[i+1] in '[]':
                answer += "', "
    ast.literal_eval("s=%s" %answer)
    return s

如果需要更多信息,请留言


1
非常抱歉没有表达清楚,但我所指的输出是一个嵌套的Python列表,这是使用pyparsing解析嵌套表达式的常见结果。您的解决方案只返回一个看起来像打印的Python列表的字符串。不过还是感谢您的帮助! - Derek
@Derek:我不是返回一个字符串,而是返回一个列表。变量名为"answer"的确是字符串,但这就是为什么有那一行代码:"exec "s=%s" %answer"。这会创建一个叫做"s"的新变量,它是一个列表。这就是为什么我的代码返回的是"s"而不是"answer"。你应该检查一下返回值的类型,你会发现它是一个列表,而不是一个字符串。 - inspectorG4dget
3
你返回了一个列表,但我认为你误解了在这个上下文中的“解析”的含义。当你解析一个字符串时,通常可以在解析时访问到匹配的标记/组,从而允许你对它们执行某些操作。你的程序只是动态生成Python代码并执行它,将字符串转换为嵌套列表。它没有解析任何内容,也没有使用原始问题中提到的pyparsing。更不用说它将执行任意的Python代码,所以例如带引号的输入就会失败。 - Derek
6
除了其他批评之外,你不应该那样使用 exec。最多,你应该使用ast.literal_eval - jpmc26
1
慎用exec -- 数据可能会运行代码以删除磁盘上的文件,上传敏感信息等。 - Michael Scott Asato Cuthbert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接