【问题标题】:Parsing a lightweight language in Python在 Python 中解析轻量级语言
【发布时间】:2013-07-31 02:32:12
【问题描述】:

假设我在 Python 中定义了一个字符串,如下所示:

my_string = "something{name1, name2, opt1=2, opt2=text}, something_else{name3, opt1=58}"

我想在 Python 中解析该字符串,以便我可以索引语言的不同结构。

例如,输出可以是字典parsing_result,它允许我以结构化方式索引不同的元素。

例如:

parsing_result['names'] 

将持有 list 的字符串:['name1', 'name2']

parsing_result['options'] 会保存一本字典,这样:

  • parsing_result['something']['options']['opt2'] 保存字符串 "text"
  • parsing_result['something_else']['options']['opt1'] 保存字符串 "58"

我的第一个问题是:如何在 Python 中解决这个问题?是否有任何库可以简化此任务?

对于一个工作示例,我不一定对解析我上面定义的确切语法的解决方案感兴趣(尽管那会很棒),但任何接近它的东西都会很棒。

更新

  1. 看起来一般正确的解决方案是使用解析器和词法分析器,例如ply(谢谢@Joran),但文档有点吓人。当语法轻量级时,有没有更简单的方法来完成这项工作?

  2. 我发现this thread 提供了以下正则表达式来围绕外部逗号分割字符串:

    r = re.compile(r'(?:[^,(]|\([^)]*\))+')
    r.findall(s)
    

    但这是假设分组字符是()(而不是{})。我正在尝试适应这一点,但这看起来并不容易。

【问题讨论】:

  • 你需要一个解析器和一个词法分析器……试试 ply for python(我经常使用的那个……)……定义一门语言的工作量很大
  • 如果语言足够轻量,可以使用正则表达式。我相信你的例子所暗示的语言就是这样的语言。
  • 如果你的语言中的句子也是Python中的句子,你可以使用ast.parse()
  • 我很好奇这只是一个练习,还是你想实现一个更大的目标?
  • @aglassman。我必须处理其他人定义的这种特定语言。展望未来,我想学习解析我自己的单行语言。

标签: python parsing


【解决方案1】:

我强烈推荐pyparsing

pyparsing 模块是另一种创建和 执行简单的语法,与传统的 lex/yacc 方法相比,或 正则表达式的使用。

语法的Python表示相当 可读,由于自解释的类名,以及使用 '+', '|'和 '^' 运算符定义。从 parseString() 返回的解析结果可以作为嵌套列表、字典或具有命名属性的对象进行访问

示例代码(来自 pyparsing 文档的 Hello world):

from pyparsing import Word, alphas
greet = Word( alphas ) + "," + Word( alphas ) + "!" # <-- grammar defined here
hello = "Hello, World!"
print (hello, "->", greet.parseString( hello ))

输出:

Hello, World! -> ['Hello', ',', 'World', '!']

编辑:这是您的示例语言的解决方案:

from pyparsing import *
import json

identifier = Word(alphas + nums + "_")
expression = identifier("lhs") + Suppress("=") + identifier("rhs")
struct_vals = delimitedList(Group(expression | identifier))
structure = Group(identifier + nestedExpr(opener="{", closer="}", content=struct_vals("vals")))
grammar = delimitedList(structure)

my_string = "something{name1, name2, opt1=2, opt2=text}, something_else{name3, opt1=58}"
parse_result = grammar.parseString(my_string)
result_list = parse_result.asList()

def list_to_dict(l):
    d = {}
    for struct in l:
        d[struct[0]] = {}
        for ident in struct[1]:
            if len(ident) == 2:
                d[struct[0]][ident[0]] = ident[1]
            elif len(ident) == 1:
                d[struct[0]][ident[0]] = None
    return d

print json.dumps(list_to_dict(result_list), indent=2)

输出:(漂亮地打印为 JSON)

{
  "something_else": {
    "opt1": "58", 
    "name3": null
  }, 
  "something": {
    "opt1": "2", 
    "opt2": "text", 
    "name2": null, 
    "name1": null
  }
}

使用pyparsing API 作为您探索 pyparsing 功能和了解我的解决方案细微差别的指南。我发现掌握这个库的最快方法是尝试一些你自己想出来的简单语言。

【讨论】:

  • 谢谢布奇。这看起来非常有希望。您提供的示例也很有帮助,但我很难理解如何定义“嵌套级别”,例如我的迷你语言中的那些(即 {} 内部的项目与外部的项目)
  • 为您的示例语言添加了解决方案。如果您对我的解决方案中使用的特定 pyparsing 函数或类有任何疑问,请告诉我。
  • 非常感谢。据您所知,有没有可以用ply 表示而不能用pyparsing 表示的语法?如果没有,pyparsing 与 ply 相关是否有任何限制?
  • 我在 lex/yacc 方面的经验并不丰富,而且我还没有玩过 ply,所以我最好只评论 pyparsing。我知道 pyparsing 能够解析任何可以用 Backus-Naur 形式 (BNF) 表达的上下文无关语法。这有帮助吗?
【解决方案2】:

正如@Joran Beasley 所说,您真的很想使用解析器和词法分析器。一开始它们并不容易让你理解,所以你想从一个非常简单的教程开始。
如果您真的在尝试编写轻量级语言,那么您将想要使用解析器/词法分析器,并学习上下文无关语法。

如果您真的只是想编写一个程序来从某些文本中删除数据,那么正则表达式将是您的最佳选择。

如果这不是编程练习,而您只是想将文本格式的结构化数据导入 python,请查看 JSON。

【讨论】:

    【解决方案3】:

    这是一个修改为对{}而不是()作出反应的正则表达式测试:

    import re
    
    s = "something{name1, name2, opt1=2, opt2=text}, something_else{name3, opt1=58}"
    r = re.compile(r'(?:[^,{]|{[^}]*})+')
    
    print r.findall(s)
    

    您将获得一个单独的“命名块”列表:

    `['something{name1, name2, opt1=2, opt2=text}', ' something_else{name3, opt1=58}']`
    

    我已经编写了更好的代码来解析你的简单示例,你应该例如捕获异常以检测语法错误,并限制更多有效的块名称、参数名称:

    import re
    
    s = "something{name1, name2, opt1=2, opt2=text}, something_else{name3, opt1=58}"
    r = re.compile(r'(?:[^,{]|{[^}]*})+')
    
    rblock = re.compile(r'\s*(\w+)\s*{(.*)}\s*')
    rparam = re.compile(r'\s*([^=\s]+)\s*(=\s*([^,]+))?')
    
    blocks =  r.findall(s)
    
    for block in blocks:
        resb = rblock.match(block)
        blockname = resb.group(1)
        blockargs = resb.group(2)
        print "block name=", blockname
        print "args:"
        for arg in re.split(",", blockargs):
            resp = rparam.match(arg)
            paramname =  resp.group(1)
            paramval = resp.group(3)
            if paramval == None:
                print "param name =\"{0}\" no value".format(paramname)
            else:
                print "param name =\"{0}\" value=\"{1}\"".format(paramname, str(paramval))
    

    【讨论】:

      猜你喜欢
      • 2010-11-16
      • 2013-10-29
      • 1970-01-01
      • 1970-01-01
      • 2011-10-14
      • 2012-01-14
      • 2016-05-15
      • 2011-06-29
      • 1970-01-01
      相关资源
      最近更新 更多