python:用BNF或pyparsing替换正则表达式
我正在解析一段相对简单的文本,每一行描述一个游戏单位。我对解析技术了解不多,所以我用了一个临时的解决方案:
class Unit:
# rules is an ordered dictionary of tagged regex that is intended to be applied in the given order
# the group named V would correspond to the value (if any) for that particular tag
rules = (
('Level', r'Lv. (?P<V>\d+)'),
('DPS', r'DPS: (?P<V>\d+)'),
('Type', r'(?P<V>Tank|Infantry|Artillery'),
#the XXX will be expanded into a list of valid traits
#note: (XXX| )* wouldn't work; it will match the first space it finds,
#and stop at that if it's in front of something other than a trait
('Traits', r'(?P<V>(XXX)(XXX| )*)'),
# flavor text, if any, ends with a dot
('FlavorText', r'(?P<V>.*\."?$)'),
)
rules = collections.OrderedDict(rules)
traits = '|'.join('All-Terrain', 'Armored', 'Anti-Aircraft', 'Motorized')
rules['Traits'] = re.sub('XXX', effects, rules['Traits'])
for x in rules:
rules[x] = re.sub('<V>', '<'+x+'>', rules[x])
rules[x] = re.compile(rules[x])
def __init__(self, data)
# data looks like this:
# Lv. 5 Tank DPS: 55 Motorized Armored
for field, regex in Item.rules.items():
data = regex.sub(self.parse, data, 1)
if data:
raise ParserError('Could not parse part of the input: ' + data)
def parse(self, m):
if len(m.groupdict()) != 1:
Exception('Expected a single named group')
field, value = m.groupdict().popitem()
setattr(self, field, value)
return ''
这个方法运行得还不错,但我感觉正则表达式的能力到了极限。特别是在处理特性(Traits)时,最后得到的值是一个字符串,我需要在后面把它拆分并转换成一个列表。例如,在这段代码中,obj.Traits 被设置为 'Motorized Armored',但在后面的函数中需要改成 ('Motorized', 'Armored')。
我在考虑把这段代码改成使用 EBNF 或者 pyparsing 语法,或者其他类似的东西。我的目标是:
- 让这段代码更整洁,减少出错的可能性
- 避免处理值列表时的麻烦(我需要先在正则表达式中做替换,然后再后处理结果,把字符串转换成列表)
你有什么建议可以用来改进代码吗?
附注:为了避免代码过于杂乱,我省略了一些部分;如果因此引入了错误,抱歉——原始代码是可以工作的 :)
1 个回答
4
我开始写一个关于pyparsing的指导手册,但看了你的规则后,发现它们可以很容易地转化为pyparsing的元素,而不需要处理EBNF,所以我快速做了一个示例:
from pyparsing import Word, nums, oneOf, Group, OneOrMore, Regex, Optional
integer = Word(nums)
level = "Lv." + integer("Level")
dps = "DPS:" + integer("DPS")
type_ = oneOf("Tank Infantry Artillery")("Type")
traits = Group(OneOrMore(oneOf("All-Terrain Armored Anti-Aircraft Motorized")))("Traits")
flavortext = Regex(r".*\.$")("FlavorText")
rule = (Optional(level) & Optional(dps) & Optional(type_) &
Optional(traits) & Optional(flavortext))
我加入了正则表达式的例子,这样你就可以看到如何把正则表达式放进现有的pyparsing语法中。使用'&'运算符组合的rule
意味着这些单独的项目可以以任何顺序出现(所以语法会自动处理所有规则的迭代,而不是你在自己的代码中去做)。pyparsing利用运算符重载来从简单的解析器构建复杂的解析器:'+'表示顺序,'|'和'^'表示选择(优先匹配或最长匹配),等等。
这是解析结果的样子——注意我添加了结果名称,就像你在正则表达式中使用命名组一样:
data = "Lv. 5 Tank DPS: 55 Motorized Armored"
parsed_data = rule.parseString(data)
print parsed_data.dump()
print parsed_data.DPS
print parsed_data.Type
print ' '.join(parsed_data.Traits)
输出结果是:
['Lv.', '5', 'Tank', 'DPS:', '55', ['Motorized', 'Armored']]
- DPS: 55
- Level: 5
- Traits: ['Motorized', 'Armored']
- Type: Tank
55
Tank
Motorized Armored
请去维基看看其他例子。你可以通过easy_install来安装pyparsing,但如果你从SourceForge下载源代码分发版,会有很多额外的文档。