this post contains at the end a handy module that I've used quite often
when I wanted to analyse the occasional complex expression and how it
was to be evaluated.
The function analyse_express ion is called with a single string argument
containing an expression. Names are allowed (actually, preferred over
numbers ;-), since the function eval's in a protected dictionary, where
names are generated as needed.
The output is a string containing many lines, where each line is of the
format:
[<operand1><spac e>]<operator><spac e><operand2>
<operand1> and 1st <space> are missing for unary operators.
There are only a few exception checks, since basically the function is
to be called with expressions pasted from actual syntactically correct
code.
Hope this helps other people, esp. newcomers in the python world.
Next step will be an tree-structured expression editor, allowing easy
editing (eg exchange the first argument of a function with the second
onei, even if these are complex expressions themselves, for people who
don't break down complex expressions as much as the Python spirit would
suggest), which could find a use inside Idle if done OK; but that will
be RSN :)
In case you find any flaws in this module, I would be glad to know and
correct them. Improvements are accepted without any arguments!
Examples:
print analyse_express ion('x+y-sqrt(5/-z.real*6)') x + y
z . real
- z.real
5 / -z.real
5/-z.real * 6
sqrt ( 5/-z.real*6 )
x+y - sqrt(5/-z.real*6)
Why names are preferred over numbers (a bug, actually ;-):
print analyse_express ion('5+sin(angl e=.6*x)')
Traceback (most recent call last):
File "<pyshell#1 6>", line 1, in -toplevel-
print analyse_express ion('5+sin(angl e=.6)')
File "analexpr.p y", line 68, in analyse_express ion
eval(code_objec t, namespace)
File "<evaluator >", line 0, in -toplevel-
TypeError: unsupported operand type(s) for +: 'int' and 'str'
but, substituting z for 5
print analyse_express ion('z+sin(angl e=.6*x)') 0.6 * x
sin ( angle=0.6*x )
z + sin(angle=0.6*x )
Don't use expressions without any names in it:
analyse_express ion('6+7-8*4') ''
cause it doesn't work... use at least one name:
print analyse_express ion('6+7-z*4') z * 4
13 - z*4
Using 'and', 'or' keywords will always behave as if their first operand
was True:
print analexpr.analys e_expression('z +7 and x+1 or y')
z + 7
x + 1
The module (no copyrights, public domain):
class EvaluationObjec t(object):
"""A helper class for analysing expressions"""
__slots__ = "_datum",
def __init__(self, datum):
self._datum = datum
def __str__(self):
return self._datum
def __call__(self, *args, **kwargs):
reply= []
reply.append(se lf._datum)
reply.append("( ")
if args:
out_arg_list1= []
for arg in args:
out_arg_list1.a ppend(str(arg). replace(' ', ''))
reply.append(', '.join(out_arg_ list1))
if kwargs:
out_arg_list2= []
for arg, value in kwargs.iteritem s():
out_arg_list2.a ppend("%s=%s" % (arg, value))
reply.append(', '.join(out_arg_ list2).replace( ' ', ''))
reply.append(") ")
rc = " ".join(repl y)
EvaluationObjec t.order.append( rc)
return rc
# create all the (EvaluationObje ct.__method__)s
def _make_binary_me thod(operator, reverse=False):
"Binary arithmetic operator factory function for EvaluationObjec t"
def _dummy(self, other):
if reverse: self, other = other, self
rc = "%s %s %s" % (str(self).repl ace(' ',''), operator,
str(other).repl ace(' ',''))
EvaluationObjec t.order.append( rc)
return EvaluationObjec t(rc)
return _dummy
# mass-make the arithmetic methods
for function in "add,+ sub,- mul,* floordiv,// mod,%" \
" pow,** lshift,<< rshift,>>" \
" and,& xor,^ or,| div,/ truediv,/" \
" getattr,.".spli t():
name, operator= function.split( ",")
setattr(Evaluat ionObject, "__%s__" % name,
_make_binary_me thod(operator))
setattr(Evaluat ionObject, "__r%s__" % name,
_make_binary_me thod(operator, reverse=True))
def _make_unary_met hod(operator):
"Unary arithmetic operator factory function for EvaluationObjec t"
def _dummy(self):
rc = "%s %s" % (operator, str(self).repla ce(' ', ''))
EvaluationObjec t.order.append( rc)
return EvaluationObjec t(rc)
return _dummy
for function in "neg,- pos,+ invert,~".split ():
name, operator = function.split( ",")
setattr(Evaluat ionObject, "__%s__" % name,
_make_unary_met hod(operator))
# cleanup
del _make_binary_me thod, _make_unary_met hod, function, name, operator
def analyse_express ion(expr):
'''Return as string a list of the steps taken to evaluate expr'''
code_object = compile(expr, "<evaluator >", "eval")
namespace = {'__builtins__' : {}}
# namespace should be a dict subclass that creates items
# on demand.
# exec and eval assume that the namespaces are dict objects
# and bypass any __getitem__ methods of the subclass
# to overcome this limitation, keep trying to eval the expression
# until no more name errors occur.
while True:
try:
EvaluationObjec t.order = []
eval(code_objec t, namespace)
except NameError, exc:
# exc.args[0] is of the form:
# name 'x' is not defined
# use hardcoded slice to get the missing name
name = exc.args[0][6:-16]
namespace[name] = EvaluationObjec t(name)
else:
break
result = '\n'.join(Evalu ationObject.ord er)
del EvaluationObjec t.order
return result
--
TZOTZIOY, I speak England very best,
Ils sont fous ces Redmontains! --Harddix