By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
448,799 Members | 1,376 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 448,799 IT Pros & Developers. It's quick & easy.

Introspection: expression evaluation order - precedence

P: n/a
Hi all,

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_expression 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><space>]<operator><space><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_expression('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_expression('5+sin(angle=.6*x)')
Traceback (most recent call last):
File "<pyshell#16>", line 1, in -toplevel-
print analyse_expression('5+sin(angle=.6)')
File "analexpr.py", line 68, in analyse_expression
eval(code_object, namespace)
File "<evaluator>", line 0, in -toplevel-
TypeError: unsupported operand type(s) for +: 'int' and 'str'

but, substituting z for 5
print analyse_expression('z+sin(angle=.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_expression('6+7-8*4') ''

cause it doesn't work... use at least one name:
print analyse_expression('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.analyse_expression('z+7 and x+1 or y')

z + 7
x + 1
The module (no copyrights, public domain):

class EvaluationObject(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(self._datum)
reply.append("(")
if args:
out_arg_list1= []
for arg in args:
out_arg_list1.append(str(arg).replace(' ', ''))
reply.append(','.join(out_arg_list1))
if kwargs:
out_arg_list2= []
for arg, value in kwargs.iteritems():
out_arg_list2.append("%s=%s" % (arg, value))
reply.append(','.join(out_arg_list2).replace(' ', ''))
reply.append(")")
rc = " ".join(reply)
EvaluationObject.order.append(rc)
return rc

# create all the (EvaluationObject.__method__)s
def _make_binary_method(operator, reverse=False):
"Binary arithmetic operator factory function for EvaluationObject"
def _dummy(self, other):
if reverse: self, other = other, self
rc = "%s %s %s" % (str(self).replace(' ',''), operator,
str(other).replace(' ',''))
EvaluationObject.order.append(rc)
return EvaluationObject(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,.".split():
name, operator= function.split(",")
setattr(EvaluationObject, "__%s__" % name,
_make_binary_method(operator))
setattr(EvaluationObject, "__r%s__" % name,
_make_binary_method(operator, reverse=True))

def _make_unary_method(operator):
"Unary arithmetic operator factory function for EvaluationObject"
def _dummy(self):
rc = "%s %s" % (operator, str(self).replace(' ', ''))
EvaluationObject.order.append(rc)
return EvaluationObject(rc)
return _dummy
for function in "neg,- pos,+ invert,~".split():
name, operator = function.split(",")
setattr(EvaluationObject, "__%s__" % name,
_make_unary_method(operator))

# cleanup
del _make_binary_method, _make_unary_method, function, name, operator

def analyse_expression(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:
EvaluationObject.order = []
eval(code_object, 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] = EvaluationObject(name)
else:
break
result = '\n'.join(EvaluationObject.order)
del EvaluationObject.order
return result

--
TZOTZIOY, I speak England very best,
Ils sont fous ces Redmontains! --Harddix
Jul 18 '05 #1
Share this question for a faster answer!
Share on Google+

This discussion thread is closed

Replies have been disabled for this discussion.