All I need is to check, using python, if a string is a valid math expression or not.
For simplicity let\'s say I just need + - * /
operators (+ -
You could try building a simple parser yourself to tokenize the string of the arithmetic expression and then build an expression tree, if the tree is valid (the leaves are all operands and the internal nodes are all operators) then you can say that the expression is valid.
The basic concept is to make a few helper functions to create your parser.
def extract()
will get the next character from the expression
def peek()
similar to extract but used if there is no whitespace to check the next character
get_expression()
get_next_token()
Alternatively if you can guarantee whitespace between characters you could use split()
to do all the tokenizing.
Then you build your tree and evaluate if its structured correctly
Try this for more info: http://effbot.org/zone/simple-top-down-parsing.htm
Why not just evaluate it and catch the syntax error?
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
Here are some samples:
> 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
It's restricted to only mathematical operations, so it should work a bit better than a crude eval
.
This is because the pyparsing code allows functions. (And by the way, it does a lot more than what you need, i.e. create a stack and evaluate that.)
For starters, you could remove pi
and ident
(and possibly something else I'm missing right now) from the code to disallow characters.
The reason is different: PyParsing parsers won't try to consume the whole input by default. You have to add + StringEnd()
(and import it, of course) to the end of expr
to make it fail if it can't parse the whole input. In that case, pyparsing.ParseException
will be raised. (Source: http://pyparsing-public.wikispaces.com/FAQs)
If you care to learn a bit of parsing, what you need can propably be built in less than thirty lines with any decent parsing library (I like LEPL).