Online Learning - Programming Languages - Python - Tech tutorial

Python’s eval(): the most powerful function you should never use.

eval() is a simple and powerful function built into Python’s interpreter. As the name implies, the function *evaluates* whatever expression is passed to it. This argument is passed as a string, with optional arguments for globals and locals, where globals is a dictionary and locals is any mapping object – usually a dictionary. This means with eval(), you can evaluate arbitrary python expressions stored as strings! More details about eval(), globals, and locals can always be found in Python’s official documentation.

Since eval() is such a powerful function, we should remember the number one lesson worth taking from Spider-man: with great power comes great responsibility.

In practice, the clearest recommendation is that one should never, ever use eval(). By sticking with this recommendation, you ensure that you are sticking to coding best practices.

  • First, as a best practice, your program’s behavior should be captured by the code itself — once you start using eval(), the behavior of the program becomes much less predictable.

Secondly, accepting untrusted input to eval() can allow for arbitrary code execution by malicious users. For example, a privileged malicious user could pass the string '__import__('subprocess').getoutput('rm -rf /')' to delete the entire filesystem, or use a utility like netcat to open a backdoor into the machine!

In nearly all cases, one can and should use native Python, or if you must execute strings, leverage functions and classes from the [subprocess module].

Let’s first consider the example presented in the official documentation:

x =1
eval(‘x + 1’)

In this case, eval() will return 2. Functionally, this is equivalent to running the expression x + 1.

So, let’s imagine we want a function that will take a single argument, x, and return the value x + 1. This, of course, is preferable, because it’s much more secure than taking arbitrary user input to eval().

def increment(x):
    return x + 1

Example usage

Now, let’s consider an example:

# Perimeter of Square
def calculatePerimeter(l):
    return 4*l

# Area of Square
def calculateArea(l):
    return l*l

expression = input("Type a function: ")

for l in range(1, 5):
    if (expression == 'calculatePerimeter(l)'):
        print("If length is ", l, ", Perimeter = ", eval(expression))
    elif (expression == 'calculateArea(l)'):
        print("If length is ", l, ", Area = ", eval(expression))
    else:
        print('Wrong Function')
        break

This is a much more complex use of eval(), and looks like a pretty compelling use of it! However, even with our expression checking, passing user input directly to eval() can be a significant security risk, and it’s a bad habit to get into.

Let’s consider how we could refactor this *not* to use eval().

# Perimeter of Square
def calculatePerimeter(l):
    return 4*l

# Area of Square
def calculateArea(l):
    return l*l

expression = input("Would you like to compute the area or the perimeter? ")

for l in range(1, 5):
    if (expression == 'perimeter'):
        print(f"If length is {l}, Perimeter = {calculatePerimeter(l)}")
    elif (expression == 'area'):
        print(f"If length is {l}, Area = {calculateArea(l)}")
    else:
        print('Wrong Function')
        break

By simply replacing the call to eval with a format string call to the function itself and rephrasing the question, we avoid getting in the habit of calling dangerous functions like eval().

Limiting execution with globals and locals

What if you’re stuck with eval() for some reason? Or maybe you’re fearless and happy to ignore the warnings not to use it! Well, in those cases, it’s important to understand how globals and locals work.

As we mentioned (and as you can find in the documentation!), globals must be a Python dict() object. This dictionary is analogous to the one returned by the globals() built-in function and consists of global variables set in the program mapped to their value. By restricting access to a subset of the dictionary of global variables, the functionality of eval() is slightly reduced.

Analogously, locals is a mapping of locally-scoped variables, usually a dictionary, that can be specified to restrict the call of eval() to some smaller set of local variables. By passing empty dictionaries to both globals and locals, eval() is not able to access values stored elsewhere in the program — something that certainly reduces its power.

Conclusion

Although using eval() is discouraged, it’s an important function to understand. It’s one of the few built-in functions that does not need to be imported from a module or the standard library, and for better or worse, you may encounter it when dealing with poorly-maintained or legacy code.

In most cases, being thoughtful about building your code can allow you to work around it, but in cases where it must be used, validating input to the function and using restricted globals and locals can make it more tolerable. If you’re going to eval(), do it responsibly!

Looking to expand your knowledge of Python? Udacity’s Programming for Data Science with Python Nanodegree will teach you the fundamentals of Python to help prepare you for a career in data science. Already familiar with Python basics, take Udacity’s Intermediate Python Nanodegree program to gain practitioner level programming skills.