Late binding eval()?

K

Kevin Smith

I want to evaluate an expression using eval(), but I only want to supply
the variable values "on demand." For example, let's say that I want to
evaluate 'x+y'. Normally, I would create a dictionary containing 'x'
and 'y' with their corresponding values and pass it in as the globals or
locals. However, in my application, I'm getting an arbitrary expression
from the user. I thought that I could just create a dictionary-like
object and override __getitem__ to look up values in a database as they
were called for, but that didn't work.

Another way of doing this would be to parse the expression and find all
of the top-level variables. This could be a little tricky (i.e. [x for
x in mylist] would only require 'mylist' to be in the dictionary of
globals/locals.

Does anyone have any ideas on how to do this?
 
J

Jeremy Bowers

However, in my application, I'm getting an arbitrary expression
from the user.

How arbitrary? Using the various parsing packages, it is easy to set up a
math calculator with any features you want, for instance, if you just mean
"arbitrary math".

If you're worried about security, this is really the only safe way to do
it. If you aren't worried about security... then I don't know enough about
what you are doing to have any other suggestions, but I would point out in
the general case even the Python interpeter can't guess in advance what
variables will be used, thanks to the magic of "getattr" and friends.
 
D

Diez B. Roggisch

Kevin said:
Does anyone have any ideas on how to do this?

A hack-approach to this migth be to eval in a loop and catch the NameError,
extract the name and bind it like this:


exp = "x * y + z"

z = 10

run = True
while run:
try:
print eval(exp)
run = False
except NameError, e:
name = e.args[0].split("'")[1]
globals()[name] = 10


Of course you can prompt the user for input, or collect the names and try to
get them at once from whereever.
 
J

Jody Winston

Kevin Smith said:
I want to evaluate an expression using eval(), but I only want to supply
the variable values "on demand." For example, let's say that I want to
evaluate 'x+y'. Normally, I would create a dictionary containing 'x'
and 'y' with their corresponding values and pass it in as the globals or
locals. However, in my application, I'm getting an arbitrary expression
from the user. I thought that I could just create a dictionary-like
object and override __getitem__ to look up values in a database as they
were called for, but that didn't work.

Another way of doing this would be to parse the expression and find all
of the top-level variables. This could be a little tricky (i.e. [x for
x in mylist] would only require 'mylist' to be in the dictionary of
globals/locals.

Does anyone have any ideas on how to do this?

Here's what I'm doing:

#! /bin/env python

import sys
import os
import tokenize
import keyword
import Numeric

class ReadlineInterface(object):
"""Present an interface that mimics readline for tokenize."""

def __init__(self, source):
"""Construct the instance."""

self.source = source
self.called = 0

def __call__(self):
"""Return the source if and only if we have not been called
before."""

if self.called == 0:
self.called = 1
return self.source
else:
return ""

class Attribute(object):
def __init__(self,
name = None,
value = 0):

self.name = name
self.value = value
return

def coerceToAttribute(value):
"""Returns value if value is an Attribute or an Attribute with
the value set."""

if isinstance(value, Attribute):
result = value
else:
result = Attribute()
result.value = value
return result

coerceToAttribute = staticmethod(coerceToAttribute)

def __add__(self, other):
"""Overloading of binary addition operator (self + other);
returns a new BaseAttribute."""

o = Attribute.coerceToAttribute(other)
result = Attribute()
result.name = self.name
result.value = self.value + o.value
return result

def __sub__(self, other):
"""Overloading of binary subtraction operator (self - other);
returns a new Attribute."""

o = Attribute.coerceToAttribute(other)
result = Attribute()
result.name = self.name
result.value = self.value - o.value
return result

class Equation(object):
def __new__(cls, *p, **k):
"""Construct a new class and correctly set up the dispatching
of properties in subclasses."""

self = object.__new__(cls, *p, **k)

# Correctly set up the dispatching of properties in subclasses.

cls.equation = property(cls.getEquation,
doc = "The equation.")

cls.created = property(cls.getCreated,
doc = "The created variables.")

cls.keywords = property(cls.getKeywords,
doc = "The keywords variable.")

cls.verbose = property(cls.getVerbose,
cls.setVerbose,
doc = "The attribute verbose. When set, the class prints more information.")
return self

def __init__(self,
equation):

"""Construct the instance."""

self.__equation = equation
self.__verbose = False

self.__created = []
self.__keywords = keyword.kwlist + ['dir',
'len',
'int',
'float',
'repr',
'abs',
'long',
'complex',
'divmod',
'max',
'min',
] + dir(Numeric)
return

def createAttribute(self,
name):
"""Build an attribute and save it's name."""


#
# Return a zeroed but otherwise useless attribute
#

result = Attribute(name = name,
value = 0)

self.__created.append(name)
return result

def localSymbols(self):
"""Return the local symbols used by the equation."""

l = locals()

#
# Add some symbols that will persist
#

if not l.has_key("keywords"):
l["keywords"] = self.keywords

return l

def createSymbols(self):
"""Determine by parsing the equation what attributes need to
be created."""

interface = ReadlineInterface(self.equation)

l = self.localSymbols()
g = globals()
k = self.keywords

try:
tokens = tokenize.generate_tokens(interface)
except tokenize.TokenError, msg:
raise TokenError(msg)

lastType = None
lastToken = None
for (tokenType, token, start, end, line) in tokens:
if self.verbose:
print 'Equation.createSymbols: type = %s, token = %s, start = %s, end = %s, line = "%s"' % \
(tokenType, token, start, end, line)

# if we are a name and the last thing that we saw was not an operator named "."
if (tokenType == tokenize.NAME) and not (lastType == tokenize.OP and lastToken == "."):
# if we have not been seen before
if (not token in k) and (not token in l) and (not token in g):
l[token] = self.createAttribute(token)

lastType = tokenType
lastToken = token

return g, l

def getEquation(self):
"""Return the equation."""

return self.__equation

def getCreated(self):
"""Return the list of created attributes."""

return self.__created

def getKeywords(self):
"""Return the list of words that are keywords."""

return self.__keywords

def getVerbose(self):
"""Return the state of verbose."""

return self.__verbose

def setVerbose(self, value):
"""Set the state of verbose."""

self.__verbose = value

return

def main(argv = None):
if argv is None:
argv = sys.argv

userSuppliedEquation = "b.value = 1; c.value = 2;a = b + c"
equ = Equation(userSuppliedEquation)
g, l = equ.createSymbols()
print "Created =", equ.created
code = compile(userSuppliedEquation, '<input>', 'exec')
eval(code, g, l)
print "a =", l['a'].value
print "b =", l['b'].value
print "c =", l['c'].value

return

if __name__ == "__main__":
sys.exit(main() or 0)
 

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
474,204
Messages
2,571,063
Members
47,670
Latest member
micheljon

Latest Threads

Top