Late binding eval()?
Jody Winston
josephwinston at mac.com
Wed Aug 25 09:32:21 EDT 2004
Kevin Smith <Kevin.Smith at sas.com> writes:
> 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)
--
Jody Winston
More information about the Python-list
mailing list