I am trying to write safyfied evaluator class, and get unexpected __builtins__ added to dictionary after evaluation. Can somebody explain why/how it happens?

import math

def echo(n):
    print n
    return n

def nothing(n):
    return n

debug = nothing

class MathParser(object):
    '''
    Mathematical Expression Evaluator class.
    call evaluate() function that will return you the result of the 
    mathematical expression given as a string when initializing.
    math library functions allowed, no __ allowed
    '''
    __slots__ = 'expression', 'variables', 'math'

    def __init__(self, expression, variables=None):
        self.math = dict((a,b) for a,b in math.__dict__.items() if not a.startswith('__') and '__' not in str(b))
        assert '__builtins__' not in self.math # passes

        self.expression = expression
        self.variables = variables
        self.check()

    def check(self):
        if any(any('__' in debug(str(n)) for n in d)
               for d in (self.expression.split(), self.math.keys(), self.math.values(), self.variables.keys(), self.variables.values())):
            raise ValueError('__ not allowed')
        return True

    def evaluate(self, variables=None):
        """ Evaluate the mathematical expression given as a string in the expression member variable.
        """
        if not variables is None:
            self.variables.update(variables)
        if self.check():        
            return eval(self.expression, self.math, self.variables)

    def __str__(self):
        return str((str(self.expression), str(self.variables), str(self.math)))

if __name__ == "__main__":

    variables = {'x':1.5, 'y':2.0}
    g = 3
    for expr in '1+ 2+sin (x+ y)/e+pi', '1+2+sin( x + y)/abc(x)', "[]+__import__('os').listdir('/')", "dir()", 'g+x' :
        try:
            p = MathParser(expr, variables)
            assert '__builtins__' not in p.math # passes
            print('%s = %s' % (expr,   p.evaluate()))
            assert '__builtins__' not in p.math # does not pass
        except (NameError, ValueError) as e:
            print(e)

    # update variable in evaluate
    debug = echo
    print(p.expression)
    print('%s = %s' % (expr,  p.evaluate()))
    print('%s = %s' % (expr,  p.evaluate({'builtins':__builtins__})))

So only the assert after p.evaluate() is failing.

Edited 4 Years Ago by pyTony

By setting the globals parameter to {"__builtins__":None} and putting everything to the math dictionary and giving it as locals, it seemed to be solved, but would be nice to understand better.

Edited 4 Years Ago by pyTony

It's always been like this: if you evaluate a python expression in a namespace, the __builtins__ dictionary is inserted in that namespace. For example

In [3]: D = dict()

In [4]: D.keys()
Out[4]: []

In [5]: eval("1+2", D)
Out[5]: 3

In [6]: D.keys()
Out[6]: ['__builtins__']

I think it's done at line 4702 in ceval.c.

Edited 4 Years Ago by Gribouillis

You can keep the import local in a function:

def mylocals():
    import math
    print(vars())

mylocals()
This question has already been answered. Start a new discussion instead.