在Python中解析轻量级语言

时间:2013-07-30 19:21:08

标签: python parsing

假设我在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)
    

    但这假设分组字符是()(而不是{})。我正在努力调整这一点,但这看起来并不容易。

3 个答案:

答案 0 :(得分:5)

我强烈推荐 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的功能并了解我的解决方案的细微差别。我发现掌握这个库的最快方法是尝试一些你自己想到的简单语言。

答案 1 :(得分:2)

以下是对{}代替()进行修改的正则表达式的测试:

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))

答案 2 :(得分:2)

正如@Joran Beasley所说,你真的想要使用解析器和词法分析器。起初它们并不容易包围,因此您需要从它们开始一个非常简单的教程 如果你真的想写一个轻量级语言,那么你将会想要使用解析器/词法分析器,并学习无上下文语法。

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

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