pythonvariablesevalabstract-syntax-tree

Why would `eval('dict('+s+')')` work but not `literal_eval('dict('+s+')')`?


I could do this with eval():

>>> s = 'hello=True,world="foobar"'
>>> eval('dict('+s+')')
{'hello': True, 'world': 'foobar'}

but with literal_eval():

>>> from ast import literal_eval
>>> literal_eval('dict('+s+')')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python@3.11/3.11.3/Frameworks/Python.framework/Versions/3.11/lib/python3.11/ast.py", line 110, in literal_eval
    return _convert(node_or_string)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/python@3.11/3.11.3/Frameworks/Python.framework/Versions/3.11/lib/python3.11/ast.py", line 109, in _convert
    return _convert_signed_num(node)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/python@3.11/3.11.3/Frameworks/Python.framework/Versions/3.11/lib/python3.11/ast.py", line 83, in _convert_signed_num
    return _convert_num(node)
           ^^^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/python@3.11/3.11.3/Frameworks/Python.framework/Versions/3.11/lib/python3.11/ast.py", line 74, in _convert_num
    _raise_malformed_node(node)
  File "/usr/local/Cellar/python@3.11/3.11.3/Frameworks/Python.framework/Versions/3.11/lib/python3.11/ast.py", line 71, in _raise_malformed_node
    raise ValueError(msg + f': {node!r}')
ValueError: malformed node or string on line 1: <ast.Call object at 0x1014cb940>

Q (part 1): Why would eval() work but not literal_eval()?

Q (part 2): Is there a name for the '+s+' style/format?

Q (part 3): Is it possible to use something like the +s+ with literal_eval()?


Solution

  • This isn't special syntax: +s+ doesn't have any atypical meaning. You're just concatenating strings together, the same way 'abc' + 'def' evaluates to 'abcdef'.

    In this case, when you concatenate the string 'dict(', the string 'hello=True,world="foobar"', and the string ')' together, you get 'dict(hello=True,world="foobar")'.

    dict(hello=True,world="foobar") is valid Python code (so eval can run it), but it's not a valid literal definition (because dict is a function call, not literal syntax), so ast.literal_eval() correctly refuses it.