既然你引用了一些测试,听起来你至少已经解决了这个问题。我假设您已经定义了一个数字,它可以是整数或实数 - 没关系,无论如何您都将所有内容转换为浮点数 - 以及两个数字的一小部分,可能是这样的:
from pyparsing import Regex, Optional
number = Regex(r"\d+(\.\d*)?").setParseAction(lambda t: float(t[0]))
fraction = number("numerator") + "/" + number("denominator")
fraction.setParseAction(lambda t: t.numerator / t.denominator)
(注意解析操作的使用,它在解析时进行浮点转换和小数除法。我更喜欢在解析时这样做,当我知道某事是数字或分数或其他东西时,而不是稍后回来并筛选通过一堆零散的字符串,试图重新创建解析器已经完成的识别逻辑。)
这是我为您的问题编写的测试用例,由整数、分数、整数和分数组成,使用整数和实数:
tests = """\
1
1.0
1/2
1.0/2.0
1 1/2
1.0 1/2
1.0 1.0/2.0""".splitlines()
for t in tests:
print t, fractExpr.parseString(t)
最后一步是如何定义一个分数表达式,它可以是一个数字、一个分数,或者一个数字和一个分数。
由于 pyparsing 是从左到右的,它不会像 regexen 那样进行回溯。所以这个表达式不会很好地工作:
fractExpr = Optional(number) + Optional(fraction)
要将可能来自数字和小数部分的数值相加,请添加以下解析操作:
fractExpr.setParseAction(lambda t: sum(t))
我们的测试打印出来:
1 [1.0]
1.0 [1.0]
1/2 [1.0]
1.0/2.0 [1.0]
1 1/2 [1.5]
1.0 1/2 [1.5]
1.0 1.0/2.0 [1.5]
对于仅包含一个分数的测试用例1/2
,前导分子与该Optional(number)
术语匹配,但我们只剩下“/2”,它与 -不匹配Optional(fraction)
- 幸运的是,由于第二个术语是可选的,因此“通过” ,但它并没有真正做到我们想要的。
我们需要让 fractExpr 更智能一点,并让它首先寻找一个孤立的分数,因为在孤立的数字和分数的前导分子之间存在这种潜在的混淆。最简单的方法是让 fractExpr 读取:
fractExpr = fraction | number + Optional(fraction)
现在有了这个改变,我们的测试结果更好了:
1 [1.0]
1.0 [1.0]
1/2 [0.5]
1.0/2.0 [0.5]
1 1/2 [1.5]
1.0 1/2 [1.5]
1.0 1.0/2.0 [1.5]
pyparsing 有几个经典的陷阱,这就是其中之一。请记住,pyparsing 仅执行您告诉它的前瞻,否则它只是从左到右的直接解析。