Solving problem is about exposing yourself to as many situations as possible like Using python’s eval() vs. ast.literal_eval()? and practice these strategies over and over. With time, it becomes second nature and a natural way you approach any problems in general. Big or small, always start with a plan, use other strategies mentioned here till you are confident and ready to code the solution.
In this post, my aim is to share an overview the topic about Using python’s eval() vs. ast.literal_eval()?, which can be followed any time. Take easy to follow this discuss.
I have a situation with some code where eval()
came up as a possible solution. Now I have never had to use eval()
before but, I have come across plenty of information about the potential danger it can cause. That said, I’m very wary about using it.
My situation is that I have input being given by a user:
datamap = input('Provide some data here: ')
Where datamap
needs to be a dictionary. I searched around and found that eval()
could work this out. I thought that I might be able to check the type of the input before trying to use the data and that would be a viable security precaution.
datamap = eval(input('Provide some data here: ')
if not isinstance(datamap, dict):
return
I read through the docs and I am still unclear if this would be safe or not. Does eval evaluate the data as soon as its entered or after the datamap
variable is called?
Is the ast
module’s .literal_eval()
the only safe option?
Answer #1:
datamap = eval(input('Provide some data here: '))
means that you actually evaluate the code before you deem it to be unsafe or not. It evaluates the code as soon as the function is called. See also the dangers of eval
.
ast.literal_eval
raises an exception if the input isn’t a valid Python datatype, so the code won’t be executed if it’s not.
Use ast.literal_eval
whenever you need eval
. You shouldn’t usually evaluate literal Python statements.
Answer #2:
ast.literal_eval()
only considers a small subset of Python’s syntax to be valid:
The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and
None
.
Passing __import__('os').system('rm -rf /a-path-you-really-care-about')
into ast.literal_eval()
will raise an error, but eval()
will happily delete your files.
Since it looks like you’re only letting the user input a plain dictionary, use ast.literal_eval()
. It safely does what you want and nothing more.
Answer #3:
eval:
This is very powerful, but is also very dangerous if you accept strings to evaluate from untrusted input. Suppose the string being evaluated is “os.system(‘rm -rf /’)” ? It will really start deleting all the files on your computer.
ast.literal_eval:
Safely evaluate an expression node or a string containing a Python literal or container display. The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, None, bytes and sets.
Syntax:
eval(expression, globals=None, locals=None)
import ast
ast.literal_eval(node_or_string)
Example:
# python 2.x - doesn't accept operators in string format
import ast
ast.literal_eval('[1, 2, 3]') # output: [1, 2, 3]
ast.literal_eval('1+1') # output: ValueError: malformed string
# python 3.0 -3.6
import ast
ast.literal_eval("1+1") # output : 2
ast.literal_eval("{'a': 2, 'b': 3, 3:'xyz'}") # output : {'a': 2, 'b': 3, 3:'xyz'}
# type dictionary
ast.literal_eval("",{}) # output : Syntax Error required only one parameter
ast.literal_eval("__import__('os').system('rm -rf /')") # output : error
eval("__import__('os').system('rm -rf /')")
# output : start deleting all the files on your computer.
# restricting using global and local variables
eval("__import__('os').system('rm -rf /')",{'__builtins__':{}},{})
# output : Error due to blocked imports by passing '__builtins__':{} in global
# But still eval is not safe. we can access and break the code as given below
s = """
(lambda fc=(
lambda n: [
c for c in
().__class__.__bases__[0].__subclasses__()
if c.__name__ == n
][0]
):
fc("function")(
fc("code")(
0,0,0,0,"KABOOM",(),(),(),"","",0,""
),{}
)()
)()
"""
eval(s, {'__builtins__':{}})
In the above code ().__class__.__bases__[0]
nothing but object itself.
Now we instantiated all the subclasses, here our main enter code here
objective is to find one class named n from it.
We need to code
object and function
object from instantiated subclasses. This is an alternative way from CPython
to access subclasses of object and attach the system.
From python 3.7 ast.literal_eval() is now stricter. Addition and subtraction of arbitrary numbers are no longer allowed. link
Answer #4:
Python’s eager in its evaluation, so eval(input(...))
(Python 3) will evaluate the user’s input as soon as it hits the eval
, regardless of what you do with the data afterwards. Therefore, this is not safe, especially when you eval
user input.
Use ast.literal_eval
.
As an example, entering this at the prompt could be very bad for you:
__import__('os').system('rm -rf /a-path-you-really-care-about')
Answer #5:
If all you need is a user provided dictionary, possible better solution is json.loads
. The main limitation is that json dicts requires string keys. Also you can only provide literal data, but that is also the case for literal_eval
.
Answer #6:
I was stuck with ast.literal_eval()
. I was trying it in IntelliJ IDEA debugger, and it kept returning None
on debugger output.
But later when I assigned its output to a variable and printed it in code. It worked fine. Sharing code example:
import ast
sample_string = '[{"id":"XYZ_GTTC_TYR", "name":"Suction"}]'
output_value = ast.literal_eval(sample_string)
print(output_value)
Its python version 3.6.