假设我在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中解决这个问题?有没有简化此任务的库?
对于一个工作示例,我不一定对解析上面定义的确切语法的解决方案感兴趣(尽管这很棒),但是接近它的任何东西都会很棒。
看起来一般正确的解决方案是使用解析器和词法分析器,例如ply(谢谢@Joran),但文档有点令人生畏。当语法轻量级时,是否有更简单的方法可以完成此操作?
我找到了this thread,其中提供了以下正则表达式来对外部逗号分隔字符串:
r = re.compile(r'(?:[^,(]|\([^)]*\))+')
r.findall(s)
但这假设分组字符是()
(而不是{}
)。我正在努力调整这一点,但这看起来并不容易。
答案 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。