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