用Python编写的数学语法检查器

10

我需要使用Python检查一个字符串是否为有效的数学表达式。

为了简单起见,假设我只需要使用+ - * /运算符(+ -也可以作为一元运算符)与数字和嵌套括号。我还添加了简单的变量名以保证完整性。

因此,我可以这样测试:

test("-3 * (2 + 1)") #valid
test("-3 * ")        #NOT valid

test("v1 + v2")      #valid
test("v2 - 2v")      #NOT valid ("2v" not a valid variable name)

我尝试了pyparsing,但只是试了这个例子:"一个简单的代数表达式解析器,可以执行+,-,*,/^算术运算"。我得到了无效代码的传递,而且在试图修复它时,总是会解析出错误的语法,而不会引发异常。 仅供尝试:
>>>test('9', 9)
9 qwerty = 9.0 ['9'] => ['9']
>>>test('9 qwerty', 9)
9 qwerty = 9.0 ['9'] => ['9']

两个测试都通过了... o_O

有什么建议吗?

5个回答

3

这是因为pyparsing代码允许使用函数。(顺便说一句,它做的远不止你需要的,也就是创建一个堆栈并评估它。)

首先,您可以从代码中删除piident(以及我现在可能忘记的其他内容),以禁止字符。

原因不同:默认情况下,PyParsing解析器不会尝试消耗整个输入。您必须在expr的末尾添加+ StringEnd()(当然还要导入它)才能使其在无法解析整个输入时失败。在这种情况下,将引发pyparsing.ParseException。(来源:http://pyparsing-public.wikispaces.com/FAQs

如果您想学习一些解析知识,那么您所需的内容可能可以使用任何体面的解析库(我喜欢LEPL)建立少于30行。


不是真的因为 pipi 而不是 querty,并且 ident 只能跟在括号后面。当然,如果我能让 pyparsing 作为一个有效的语法检查器工作,我会很喜欢它。我也会给 LEPL 一个机会。 - neurino
@neuriono:那么要么源代码具有误导性,语法实际上是不同的,要么pyparsing出现了问题(编辑:我能想到的一个解释属于“pyparsing出现了问题”类别:如果剩余输入无法解析,则它不会消耗整个字符串,而是退出并返回它已经解析的内容)。 - user395760
这很明显,但是如果你看一下构建解析器的代码部分(def BNF()),它非常简单,即使删除像_exponentiation_这样的部分,使其更简单,它仍然失败了,所以我猜pyparsing在检查语法方面并不好。 - neurino
@neuriono:我的猜测是正确的。已将原因和解决方案添加到答案中。 - user395760
1
感谢您指出这一点,我会看看是否真的可以检查我的语法并给您最好的答案。 - neurino

2
parseAll=True添加到对parseString的调用中,将会把这个解析器转换成一个验证器。

1
有点晚了...(请看我对所选答案的最后一条评论),不过还是谢谢。 - neurino

1
为什么不直接评估它并捕获语法错误呢?
from math import *

def validateSyntax(expression):
  functions = {'__builtins__': None}
  variables = {'__builtins__': None}

  functions = {'acos': acos,
               'asin': asin,
               'atan': atan,
               'atan2': atan2,
               'ceil': ceil,
               'cos': cos,
               'cosh': cosh,
               'degrees': degrees,
               'exp': exp,
               'fabs':fabs,
               'floor': floor,
               'fmod': fmod,
               'frexp': frexp,
               'hypot': hypot,
               'ldexp': ldexp,
               'log': log,
               'log10': log10,
               'modf': modf,
               'pow': pow,
               'radians': radians,
               'sin': sin,
               'sinh': sinh,
               'sqrt': sqrt,
               'tan': tan,
               'tanh': tanh}

  variables = {'e': e, 'pi': pi}

  try:
    eval(expression, variables, functions)
  except (SyntaxError, NameError, ZeroDivisionError):
    return False
  else:
    return True

以下是一些示例:

> print validSyntax('a+b-1') # a, b are undefined, so a NameError arises.
> False

> print validSyntax('1 + 2')
> True

> print validSyntax('1 - 2')
> True

> print validSyntax('1 / 2')
> True

> print validSyntax('1 * 2')
> True

> print validSyntax('1 +/ 2')
> False

> print validSyntax('1 + (2')
> False

> print validSyntax('import os')
> False

> print validSyntax('print "asd"')
> False

> print validSyntax('import os; os.delete("~\test.txt")')
> False # And the file was not removed

它仅限于数学运算,因此它应该比粗糙的eval更有效。


2
这比第一个(现已删除)答案要糟糕得多,因为它至少检查了答案是否仅由数字和运算符组成。而你的则允许任意代码 :( - user395760
literal_eval 不是答案,因为您希望允许数学运算符和括号。 - user395760
另外更新一下:我已经更改了literal_eval的源代码,使其仅接受二元和一元操作(希望现在它很干净)。 - Blender
1
@Blender:即使需求扩展,使用解析库的解决方案也可以轻松调整,无需手动编写解决方案。 - user395760
我明白你的意思...让我看看我能做什么。 - Blender
显示剩余10条评论

1
你可以尝试自己构建一个简单的解析器来对算术表达式进行分词,并构建表达式树,如果树是有效的(叶子节点都是操作数,内部节点都是运算符),那么你就可以说这个表达式是有效的。
基本概念是要创建一些帮助函数来创建你的解析器。 def extract() 将从表达式中获取下一个字符
def peek() 与 extract 类似,但用于检查下一个字符是否有空格
get_expression()
get_next_token() 或者,如果你能保证字符之间有空格,你可以使用 split() 来进行所有的分词。
然后你可以构建你的树并评估它是否结构正确。
请尝试查看此链接以获取更多信息:http://effbot.org/zone/simple-top-down-parsing.htm

自己构建所有这些已经过时了...现在,您可以使用一个解析库来处理所有令人讨厌的官僚主义。 - user395760
@Yoel:那你就没什么运气了,可能你的标准太高了。 - user395760
@delnan,我不明白为什么有人要编写你喜欢使用的LEPL库。我想可能是因为我希望做一些“上个世纪的事情”,算我老派吧 ;) - Jordan
1
@Yoel:我猜你已经手动解析过一些非平凡的东西了(CSV处于平凡和简单之间)。我曾经也这样做过,但是在中途我意识到我正在编写帮助函数、实用程序等,而解析库已经提供了这些功能(更不用说他们的代码运行得很完美,而我的代码仍然有缺陷)。 - user395760
@delnan:赞同不让这一系列评论变得激烈。我理解你的想法,确实使用经过验证的库非常有用。但是对于@neurino所需的特定应用程序,我认为采用自定义路线可能是有意义的。我想我对空间和依赖性的限制做出了过早的结论。 - Jordan
显示剩余2条评论

0

如果您有兴趣将一个用Python编写的自定义数学求值引擎修改为验证器,您可以从Evaluator 2.0(Python 3.x)和Math_Evaluator(Python 2.x)开始。它们不是现成的解决方案,但可以让您使用易于阅读的Python代码完全自定义您想要做的任何事情。请注意,“and”和“or”被视为运算符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接