[New-bugs-announce] [issue31753] Unnecessary closure in ast.literal_eval

Aaron Hall report at bugs.python.org
Tue Oct 10 18:02:07 EDT 2017


New submission from Aaron Hall <aaronchall at yahoo.com>:

Removing the closure seems to make the function about 10% faster.

Original source code at: https://github.com/python/cpython/blob/3.6/Lib/ast.py#L40

Empirical evidence: astle.py

import timeit
from ast import literal_eval as orig_literal_eval
from ast import *

def new_literal_eval(node_or_string):
    """
    Safely evaluate an expression node or a string containing a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
    sets, booleans, and None.
    """
    if isinstance(node_or_string, str):
        node_or_string = parse(node_or_string, mode='eval')
    if isinstance(node_or_string, Expression):
        node_or_string = node_or_string.body
    node = node_or_string
    if isinstance(node, Constant):
        return node.value
    elif isinstance(node, (Str, Bytes)):
        return node.s
    elif isinstance(node, Num):
        return node.n
    elif isinstance(node, Tuple):
        return tuple(map(_convert, node.elts))
    elif isinstance(node, List):
        return list(map(_convert, node.elts))
    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))
    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):
            if isinstance(node.op, Add):
                return left + right
            else:
                return left - right
    raise ValueError('malformed node or string: ' + repr(node))



def main():
    print('orig first, then new')
    print("'1.01'")
    print(min(timeit.repeat(lambda: orig_literal_eval('1.01'))))
    print(min(timeit.repeat(lambda: new_literal_eval('1.01'))))
    print("""'"1.01"'""")
    print(min(timeit.repeat(lambda: orig_literal_eval('"1.01"'))))
    print(min(timeit.repeat(lambda: new_literal_eval('"1.01"'))))
    print("'1'")
    print(min(timeit.repeat(lambda: orig_literal_eval('1'))))
    print(min(timeit.repeat(lambda: new_literal_eval('1'))))


if __name__ == '__main__':
    main()

Shell:

$ python -m astle
orig first, then new
'1.01'
3.518230145502848
3.274753015923377
'"1.01"'
3.189016693752965
2.906869704238048
'1'
3.40557457956146
3.157061471625788

----------
components: Library (Lib)
messages: 304089
nosy: Aaron Hall
priority: normal
severity: normal
status: open
title: Unnecessary closure in ast.literal_eval
type: performance

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue31753>
_______________________________________


More information about the New-bugs-announce mailing list