假设我在Python中定义了一个字符串,如下所示:
my_string = "something{name1, name2, opt1=2, opt2=text}, something_else{name3, opt1=58}"
我想用Python解析这个字符串,使我能够索引该语言的不同结构。
例如,输出可以是字典parsing_result
,它允许我以结构化的方式对不同的元素进行索引。
例如,以下内容:
parsing_result['names']
将保存字符串的list
:['name1', 'name2']
而CCD_ 4将保存字典,使得:
parsing_result['something']['options']['opt2']
保存字符串"text"
parsing_result['something_else']['options']['opt1']
保存字符串"58"
我的第一个问题是:如何在Python中处理这个问题?有没有简化这项任务的库?
对于一个工作示例,我不一定对解析我上面定义的确切语法的解决方案感兴趣(尽管这将是非常棒的),但任何接近它的解决方案都会很棒。
更新
看起来一般正确的解决方案是使用解析器和lexer,如ply(谢谢@Joran),但文档有点吓人。当语法是轻量级时,有没有更简单的方法可以做到这一点?
我发现了这个线程,其中提供了以下正则表达式来围绕外部逗号对字符串进行分区:
r = re.compile(r'(?:[^,(]|([^)]*))+') r.findall(s)
但这是假设分组字符是
()
(而不是{}
)。我正在努力适应,但这看起来并不容易。
我强烈建议pyparsing:
pyparsing模块是创建和执行简单语法,与传统的lex/yacc方法相比,或者正则表达式的使用。
语法的Python表示相当可读,因为不言自明的类名以及"+"、"|"one_answers"^"运算符定义。从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作为您的指南来探索pyparcing的功能并理解我的解决方案的细微差别。我发现掌握这个库的最快方法是在你自己想出的一些简单语言上进行尝试。
正如@Joran Beasley所说,您确实希望使用解析器和lexer。一开始你不容易理解它们,所以你想从一个非常简单的教程开始
如果你真的想写一种轻量级语言,那么你会想使用解析器/lexer,学习上下文无关语法。
如果你真的只是想写一个程序来从一些文本中提取数据,那么正则表达式就是最好的选择。
如果这不是一个编程练习,并且您只是试图将文本格式的结构化数据获取到python中,请查看JSON。
这里是一个正则表达式的测试,它被修改为对{}
而不是()
:做出反应
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))