Evaluating Python expressions
Python provides several ways to interact with the interpreter from within a program. For example, the eval function evaluates a string as if it were a Python expression. You can pass it a literal, simple expressions, or even use built-in functions:
# File: builtin-eval-example-1.py def dump(expression): result = eval(expression) print expression, "=>", result, type(result) dump("1") dump("1.0") dump("'string'") dump("1.0 + 2.0") dump("'*' * 10") dump("len('world')")
1 => 1 <type 'int'> 1.0 => 1.0 <type 'float'> 'string' => string <type 'string'> 1.0 + 2.0 => 3.0 <type 'float'> '*' * 10 => ********** <type 'string'> len('world') => 5 <type 'int'>
A problem with eval is that if you cannot trust the source from which you got the string, you may get into trouble. For example, someone might use the built-in __import__ function to load the os module, and then remove files on your disk:
# File: builtin-eval-example-2.py print eval("__import__('os').getcwd()") print eval("__import__('os').remove('file')")
/home/fredrik/librarybook Traceback (innermost last): File "builtin-eval-example-2", line 2, in ? File "<string>", line 0, in ? os.error: (2, 'No such file or directory')
Note that you get an os.error exception, which means that Python actually tried to remove the file!
Luckily, there’s a way around this problem. You can pass a second argument to eval, which should contain a dictionary defining the namespace in which the expression is evaluated. Let’s pass in an empty namespace:
>>> print eval("__import__('os').remove('file')", {}) Traceback (innermost last): File "<stdin>", line 1, in ? File "<string>", line 0, in ? os.error: (2, 'No such file or directory')
Hmm. We still end up with an os.error exception.
The reason for this is that Python looks in the dictionary before it evaluates the code, and if it doesn’t find a variable named __builtins__ in there (note the plural form), it adds one:
>>> namespace = {} >>> print eval("__import__('os').remove('file')", namespace) Traceback (innermost last): File "<stdin>", line 1, in ? File "<string>", line 0, in ? os.error: (2, 'No such file or directory') >>> namespace.keys() ['__builtins__']
If you print the contents of the namespace variable, you’ll find that it contains the full set of built-in functions.
The solution to this little dilemma isn’t far away: since Python doesn’t add this item if it is already there, you just have to add a dummy item called __builtins__ to the namespace before calling eval:
# File: builtin-eval-example-3.py print eval("__import__('os').getcwd()", {}) print eval("__import__('os').remove('file')", {"__builtins__": {}})
/home/fredrik/librarybook Traceback (innermost last): File "builtin-eval-example-3.py", line 2, in ? File "<string>", line 0, in ? NameError: __import__
Note that this doesn’t protect you from CPU or memory resource attacks (for example, something like eval(“’*’*1000000*2*2*2*2*2*2*2*2*2”) will most likely cause your program to run out of memory after a while)