Introspection: expression evaluation order - precedence

  • Thread starter Christos TZOTZIOY Georgiou
  • Start date
C

Christos TZOTZIOY Georgiou

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:
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 ;-):

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
0.6 * x
sin ( angle=0.6*x )
z + sin(angle=0.6*x)

Don't use expressions without any names in it:
''

cause it doesn't work... use at least one name:
z * 4
13 - z*4

Using 'and', 'or' keywords will always behave as if their first operand
was True:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top