Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ def parse(source, filename='<unknown>', mode='exec'):
return compile(source, filename, mode, PyCF_ONLY_AST)


_NUM_TYPES = (int, float, complex)

def literal_eval(node_or_string):
"""
Safely evaluate an expression node or a string containing a Python
Expand All @@ -48,6 +46,21 @@ def literal_eval(node_or_string):
node_or_string = parse(node_or_string, mode='eval')
if isinstance(node_or_string, Expression):
node_or_string = node_or_string.body
def _convert_num(node):
if isinstance(node, Constant):
if isinstance(node.value, (int, float, complex)):
return node.value
elif isinstance(node, Num):
return node.n
raise ValueError('malformed node or string: ' + repr(node))
def _convert_signed_num(node):
if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)):
operand = _convert_num(node.operand)
if isinstance(node.op, UAdd):
return + operand
else:
return - operand
return _convert_num(node)
def _convert(node):
if isinstance(node, Constant):
return node.value
Expand All @@ -62,26 +75,19 @@ def _convert(node):
elif isinstance(node, Set):
return set(map(_convert, node.elts))
elif isinstance(node, Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
return dict(zip(map(_convert, node.keys),
map(_convert, node.values)))
elif isinstance(node, NameConstant):
return node.value
elif isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)):
operand = _convert(node.operand)
if isinstance(operand, _NUM_TYPES):
if isinstance(node.op, UAdd):
return + operand
else:
return - operand
elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)):
left = _convert(node.left)
right = _convert(node.right)
if isinstance(left, _NUM_TYPES) and isinstance(right, _NUM_TYPES):
left = _convert_signed_num(node.left)
right = _convert_num(node.right)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it would be useful to add to the test assertRaises(ValueError, ast.literal_eval, '3 + (0 + 2j)') to catch unintended behavior changes to _convert_num in future edits.

if isinstance(left, (int, float)) and isinstance(right, complex):
if isinstance(node.op, Add):
return left + right
else:
return left - right
raise ValueError('malformed node or string: ' + repr(node))
return _convert_signed_num(node)
return _convert(node_or_string)


Expand Down
39 changes: 31 additions & 8 deletions Lib/test/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,14 +551,37 @@ def test_literal_eval(self):
self.assertEqual(ast.literal_eval('{1, 2, 3}'), {1, 2, 3})
self.assertEqual(ast.literal_eval('b"hi"'), b"hi")
self.assertRaises(ValueError, ast.literal_eval, 'foo()')
self.assertEqual(ast.literal_eval('6'), 6)
self.assertEqual(ast.literal_eval('+6'), 6)
self.assertEqual(ast.literal_eval('-6'), -6)
self.assertEqual(ast.literal_eval('-6j+3'), 3-6j)
self.assertEqual(ast.literal_eval('3.25'), 3.25)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it would also be useful to assertRaises(ValueError for an expression such as 1j+2j or 1+2j+3j.


def test_literal_eval_issue4907(self):
self.assertEqual(ast.literal_eval('2j'), 2j)
self.assertEqual(ast.literal_eval('10 + 2j'), 10 + 2j)
self.assertEqual(ast.literal_eval('1.5 - 2j'), 1.5 - 2j)
self.assertEqual(ast.literal_eval('+3.25'), 3.25)
self.assertEqual(ast.literal_eval('-3.25'), -3.25)
self.assertEqual(repr(ast.literal_eval('-0.0')), '-0.0')
self.assertRaises(ValueError, ast.literal_eval, '++6')
self.assertRaises(ValueError, ast.literal_eval, '+True')
self.assertRaises(ValueError, ast.literal_eval, '2+3')

def test_literal_eval_complex(self):
# Issue #4907
self.assertEqual(ast.literal_eval('6j'), 6j)
self.assertEqual(ast.literal_eval('-6j'), -6j)
self.assertEqual(ast.literal_eval('6.75j'), 6.75j)
self.assertEqual(ast.literal_eval('-6.75j'), -6.75j)
self.assertEqual(ast.literal_eval('3+6j'), 3+6j)
self.assertEqual(ast.literal_eval('-3+6j'), -3+6j)
self.assertEqual(ast.literal_eval('3-6j'), 3-6j)
self.assertEqual(ast.literal_eval('-3-6j'), -3-6j)
self.assertEqual(ast.literal_eval('3.25+6.75j'), 3.25+6.75j)
self.assertEqual(ast.literal_eval('-3.25+6.75j'), -3.25+6.75j)
self.assertEqual(ast.literal_eval('3.25-6.75j'), 3.25-6.75j)
self.assertEqual(ast.literal_eval('-3.25-6.75j'), -3.25-6.75j)
self.assertEqual(ast.literal_eval('(3+6j)'), 3+6j)
self.assertRaises(ValueError, ast.literal_eval, '-6j+3')
self.assertRaises(ValueError, ast.literal_eval, '-6j+3j')
self.assertRaises(ValueError, ast.literal_eval, '3+-6j')
self.assertRaises(ValueError, ast.literal_eval, '3+(0+6j)')
self.assertRaises(ValueError, ast.literal_eval, '-(3+6j)')

def test_bad_integer(self):
# issue13436: Bad error message with invalid numeric values
Expand Down Expand Up @@ -1077,11 +1100,11 @@ def test_literal_eval(self):
ast.copy_location(new_left, binop.left)
binop.left = new_left

new_right = ast.Constant(value=20)
new_right = ast.Constant(value=20j)
ast.copy_location(new_right, binop.right)
binop.right = new_right

self.assertEqual(ast.literal_eval(binop), 30)
self.assertEqual(ast.literal_eval(binop), 10+20j)


def main():
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2061,7 +2061,7 @@ def p(name): return signature.parameters[name].default
self.assertEqual(p('f'), False)
self.assertEqual(p('local'), 3)
self.assertEqual(p('sys'), sys.maxsize)
self.assertEqual(p('exp'), sys.maxsize - 1)
self.assertNotIn('exp', signature.parameters)

test_callable(object)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ast.literal_eval() is now more strict. Addition and subtraction of
arbitrary numbers no longer allowed.