2

假设我在 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. 我发现这个线程提供了以下正则表达式来围绕外部逗号分割字符串:

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

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

4

3 回答 3

5

我强烈推荐pyparsing

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

由于类名一目了然,并且使用了“+”、“|” ,因此该语法的 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 功能和了解我的解决方案的细微差别的指南。我发现掌握这个库的最快方法是在您自己想出的一些简单语言上进行尝试。

于 2013-07-30T20:58:02.783 回答
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))
于 2013-07-30T19:59:46.827 回答
2

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

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

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

于 2013-07-30T20:03:14.047 回答