Commits

Victor Stinner  committed 82f1110

use literal list and set

  • Participants
  • Parent commits d7fe24b

Comments (0)

Files changed (6)

    - ``iter(set())`` => ``iter(())``
    - ``frozenset("")`` => ``frozenset()``
    - ``(x for x in "abc" if False)`` => ``iter(())``
+   - ``tuple(x for x in "abc")`` => ``("a", "b", "c")``
    - ``[x for x in ""]`` => ``[]``
    - ``[x for x in iterable]`` => ``list(iterable)``
-   - ``set([x for x in "abc"])`` => ``set("abc")``
-   - ``tuple(x for x in "abc")`` => ``tuple("abc")``
+   - ``set([x for x in "abc"])`` => ``set(("a", "b", "c"))``
 
  * Replace list with tuple (need "builtin_funcs" feature). Examples:
 
    - ``for x in [1, 2, 3]: ...`` => ``for x in (1, 2, 3): ...``
-   - ``list([x, y, z])`` => ``list((x, y, z))``
-   - ``set([1, 2, 3])`` => ``set((1, 2, 3))``
+   - ``list([x, y, z])`` => ``[x, y, z]``
+   - ``set([1, 2, 3])`` => ``{1, 2, 3}``
 
  * Evaluate unary and binary operators, subscript and comparaison if all
    arguments are constants. Examples:

File astoptimizer/ast_tools.py

 import ast
 from astoptimizer.compatibility import (
     PYTHON3, COMPLEX_TYPES, BYTES_TYPE, UNICODE_TYPE)
+import sys
 
 def copy_lineno(node, new_node):
     ast.fix_missing_locations(new_node)
         raise NotImplementedError("unable to create an AST object for constant: %r" % (value,))
     return copy_lineno(node, new_node)
 
+if sys.version_info >= (2, 7):
+    def new_set(node, iterable=()):
+        elts = [new_constant(node, elt) for elt in iterable]
+        new_node = ast.Set(elts=elts)
+        return copy_lineno(node, new_node)
+
 def iter_all_ast(node):
     yield node
     for field, value in ast.iter_fields(node):

File astoptimizer/config.py

         # type). CPython peephole optimizer uses a limit of 20.
         self.max_string_length = 4096
 
-        # Maximum number of items of a tuple or a frozenset
+        # Maximum number of items of a frozenset, set or tuple
         self.max_tuple_length = 20
 
         # Remove dead code?

File astoptimizer/config_builtin_funcs.py

             return False
     return True
 
-def check_tuple_args(config, args):
-    if len(args) == 0:
-        return True
-    arg = args[0]
-    if not isinstance(arg, IMMUTABLE_ITERABLE_TYPES):
-        return False
-    if len(arg) > config.max_tuple_length:
-        return False
-    return all(isinstance(item, IMMUTABLE_TYPES) for item in arg)
-
 def check_len(config, result):
     return (result <= config.max_size)
 
     else:
         config.add_func('str', Function(str, 1, COMPLEX_TYPES + STR_TYPES, check_args=check_str_args))
     config.add_func('sum', Function(sum, (1, 2), (tuple, frozenset), COMPLEX_TYPES, check_args=check_sum_args))
-    config.add_func('tuple', Function(tuple, (0, 1), IMMUTABLE_ITERABLE_TYPES, check_args=check_tuple_args))
 
     if PYTHON2:
         config.add_func('long', Function(long, 1, FLOAT_TYPES))

File astoptimizer/optimizer.py

 import ast
 import operator
+import sys
+
 from astoptimizer import UNSET
 from astoptimizer.ast_tools import (
     copy_lineno,
     new_constant, new_call, new_pass,
     ast_contains, check_func_args)
+if sys.version_info >= (2, 7):
+    from astoptimizer.ast_tools import new_set
 from astoptimizer.config import optimize_unicode
 from astoptimizer.compatibility import (
     u,
     INT_TYPES, FLOAT_TYPES, COMPLEX_TYPES,
     BYTES_TYPE, UNICODE_TYPE, STR_TYPES,
     IMMUTABLE_ITERABLE_TYPES)
-import sys
 
 DROP_NODE = object()
 
         if self.check_builtin_func(node, BUILTIN_ACCEPTING_ITERABLE, 0, 0):
             return True
 
+        # iter(())
+        if (self.check_builtin_func(node, 'iter', 1, 1)
+        and isinstance(node.args[0], ast.Tuple)
+        and not node.args[0].elts):
+            return True
+
+        if (isinstance(node, ast.Dict)
+        and not node.keys
+        and not node.values):
+            return True
+
+        if (sys.version_info >= (2, 7)
+        and isinstance(node, ast.Set)
+        and not node.elts):
+            return True
+
+        return False
+
+    def call_builtin_iterable(self, node):
+        qualname = self.namespace.get_qualname(node.func.id)
+        if len(node.args) == 1:
+            arg = node.args[0]
+            new_arg = self.optimize_iter(arg)
+        else:
+            arg = UNSET
+            new_arg = DROP_NODE
+        if new_arg is DROP_NODE:
+            if qualname == 'tuple':
+                return new_constant(node, ())
+            if qualname == 'list':
+                new_node = ast.List(elts=[], ctx=ast.Load())
+                return copy_lineno(node, new_node)
+            if qualname == 'dict':
+                new_node = ast.Dict(keys=[], values=[])
+                return copy_lineno(node, new_node)
+            if (sys.version_info >= (2, 7)
+            and qualname == 'set'):
+                return new_set(node)
+            if arg is not UNSET:
+                del node.args[0]
+            return
+
+        if qualname in ('frozenset', 'set', 'tuple'):
+            if new_arg is not None:
+                constant = self.get_constant(new_arg)
+            else:
+                constant = self.get_constant(arg)
+            if (constant is not UNSET
+            and isinstance(constant, IMMUTABLE_ITERABLE_TYPES)):
+                if qualname != 'tuple':
+                    elts = frozenset(constant)
+                    try:
+                        # sort elements for astoptimizer unit tests
+                        elts = list(elts)
+                        elts.sort()
+                    except TypeError:
+                        # elements may be unsortable
+                        pass
+
+                    if len(elts) <= self.config.max_tuple_length:
+                        if (qualname == 'set'
+                        and sys.version_info >= (2, 7)):
+                            return new_set(node, elts)
+                        else:
+                            new_arg = new_constant(arg, tuple(elts))
+                else:
+                    # qualname == 'tuple'
+                    if len(constant) <= self.config.max_tuple_length:
+                        return new_constant(node, tuple(constant))
+        elif qualname == 'list':
+            if new_arg is not None:
+                arg = new_arg
+            if isinstance(arg, ast.Tuple):
+                new_node = ast.List(elts=arg.elts, ctx=ast.Load())
+                return copy_lineno(node, new_node)
+
+        if new_arg is None:
+            return
+        node.args[0] = new_arg
+
     def call_name(self, node):
         name = self.namespace.get_qualname(node.func.id)
         if name is not UNSET:
             if new_node is not UNSET:
                 return new_node
 
-        if self.check_builtin_func(node, BUILTIN_ACCEPTING_ITERABLE,  1, 1):
-            arg = node.args[0]
-            new_arg = self.optimize_iter(arg)
-            if new_arg is DROP_NODE:
-                qualname = self.namespace.get_qualname(node.func.id)
-                if qualname == 'list':
-                    new_node = ast.List(elts=[], ctx=ast.Load())
-                    return copy_lineno(node, new_node)
-                if qualname == 'dict':
-                    new_node = ast.Dict(keys=[], values=[])
-                    return copy_lineno(node, new_node)
-                del node.args[0]
-                return
-            if new_arg is not None:
-                node.args[0] = new_arg
-                return
-
-            if (self.config.remove_dead_code
-            and self.check_builtin_func(arg, ('list', 'tuple'),  1, 1)):
-                # set(list(iterable)) => set(iterable)
-                node.args[0] = arg.args[0]
-                return
+        if self.check_builtin_func(node, BUILTIN_ACCEPTING_ITERABLE,  0, 1):
+            return self.call_builtin_iterable(node)
 
         elif (self.check_builtin_func(node, 'iter', 1, 1)
         and self.is_empty_iterable(node.args[0])):
             # set(iter(iterable)) => set(iterable)
             return iter_arg
 
+        if (self.config.remove_dead_code
+        and self.check_builtin_func(node, ('list', 'tuple'),  1, 1)):
+            # set(list(iterable)) => set(iterable)
+            return node.args[0]
+
     def list_to_tuple(self, node):
         if len(node.elts) > self.config.max_tuple_length:
             return node

File astoptimizer/tests.py

     def text_tuple(self, *elts):
         return self._text_module(self._text_tuple(*elts))
 
+    def _text_call(self, name, *args):
+        func = self._text_name(name)
+        args = list(args)
+        return (
+            'Call(func=%s, args=[%s], '
+            'keywords=[], starargs=None, kwargs=None)'
+            % (func, ', '.join(args)))
+
+    def _text_set(self, *elts):
+        if sys.version_info >= (2, 7):
+            elts = (self._text_item(elt) for elt in elts)
+            elts = '[%s]' % ', '.join(elts)
+            return 'Set(elts=%s)' % elts
+        else:
+            if elts:
+                arg = self._text_tuple(*elts)
+                return self._text_call('set', arg)
+            else:
+                return self._text_call('set')
+
+    def text_set(self, *elts):
+        return self._text_module(self._text_set(*elts))
+
     def text_ast(self, code):
         tree = parse_ast(code)
         return ast.dump(tree)
     def test_max_tuple_length(self):
         config = self.create_config('builtin_funcs')
         config.max_tuple_length = 3
+
         self.check('len((1, 2, 3))', self.text_num(3), config)
         self.check_not_optimized('len((1,2,3,4))', config)
+
         self.check('len(frozenset("abc"))', self.text_num(3), config)
-        self.check_not_optimized('len(frozenset("abcd"))', config)
+        self.check_not_optimized('len(frozenset("dcba"))', config)
+
+        self.check('set("abc")', self.text_set("a", "b", "c"), config)
+        self.check('set("abcabc")', self.text_set("a", "b", "c"), config)
+        self.check_not_optimized('set("abcd")', config)
 
     def test_iter_empty_iterable(self):
         config = self.create_config('builtin_funcs')
         self.check('dict(iter(()))', self.text_ast('{}'), config)
         self.check('frozenset(iter(()))', self.text_ast('frozenset()'), config)
         self.check('list(iter(()))', self.text_ast('[]'), config)
-        self.check('set(iter(()))', self.text_ast('set()'), config)
-        self.check('tuple(iter(()))', self.text_ast('tuple()'), config)
+        self.check('set(iter(()))', self.text_set(), config)
+        self.check('tuple(iter(()))', self.text_tuple(), config)
 
     def test_drop_empty_iterable(self):
         config = self.create_config('builtin_funcs')
-        self.check('set(())', self.text_ast('set()'), config)
-        self.check('set([])', self.text_ast('set()'), config)
-        self.check('set(tuple())', self.text_ast('set()'), config)
-        self.check('set(list())', self.text_ast('set()'), config)
-        self.check('set(dict())', self.text_ast('set()'), config)
-        self.check('set(set())', self.text_ast('set()'), config)
-        self.check('set(frozenset())', self.text_ast('set()'), config)
+        empty_set = self.text_set()
+        self.check('set(())', empty_set, config)
+        self.check('set([])', empty_set, config)
+        self.check('set(tuple())', empty_set, config)
+        self.check('set(list())', empty_set, config)
+        self.check('set(dict())', empty_set, config)
+        self.check('set(set())', empty_set, config)
+        self.check('set(frozenset())', empty_set, config)
 
     def test_drop_builtin_iterable(self):
         config = self.create_config('builtin_funcs')
 
     def test_drop_iter_empty_iterable(self):
         config = self.create_config('builtin_funcs')
-        self.check('tuple(iter(""))', self.text_ast('tuple()'), config)
-        self.check('tuple(iter(()))', self.text_ast('tuple()'), config)
-        self.check('tuple(iter([]))', self.text_ast('tuple()'), config)
-        self.check('tuple(iter(tuple()))', self.text_ast('tuple()'), config)
-        self.check('tuple(iter(list()))', self.text_ast('tuple()'), config)
-        self.check('tuple(iter(dict()))', self.text_ast('tuple()'), config)
-        self.check('tuple(iter(set()))', self.text_ast('tuple()'), config)
-        self.check('tuple(iter(frozenset()))', self.text_ast('tuple()'), config)
+        empty_tuple = self.text_tuple()
+        self.check('tuple(iter(""))', empty_tuple, config)
+        self.check('tuple(iter(()))', empty_tuple, config)
+        self.check('tuple(iter([]))', empty_tuple, config)
+        self.check('tuple(iter(tuple()))', empty_tuple, config)
+        self.check('tuple(iter(list()))', empty_tuple, config)
+        self.check('tuple(iter(dict()))', empty_tuple, config)
+        self.check('tuple(iter(set()))', empty_tuple, config)
+        self.check('tuple(iter(frozenset()))', empty_tuple, config)
 
     def test_GeneratorExp(self):
         config = self.create_config('builtin_funcs')
 
         # tuple(generator)
         self.check('tuple(x for x in "abc")',
-                   self.text_ast('tuple("abc")'),
+                   self.text_tuple("a", "b", "c"),
                    config)
         self.check('tuple(x*2 for x in "abc" if 0)',
-                   self.text_ast('tuple()'),
+                   self.text_tuple(),
                    config)
         self.check('tuple(x for x in iterable)',
                    self.text_ast('tuple(iterable)'),
 
         # tuple(list comprehesion)
         self.check('tuple([x for x in "abc"])',
-                   self.text_ast('tuple("abc")'),
+                   self.text_tuple("a", "b", "c"),
                    config)
         self.check('tuple([x for x in "abc" if False])',
-                   self.text_ast('tuple()'),
+                   self.text_tuple(),
                    config)
         self.check('tuple([x for x in iterable])',
                    self.text_ast('tuple(iterable)'),
                        config)
 
         self.check('set([x for x in range(3)])',
-                   self.text_ast('set((0, 1, 2))'),
+                   self.text_set(0, 1, 2),
                    config)
 
 class TestFrozenset(BaseTestCase):
         self.check('"b" in frozenset("abc")', self.text_bool(True))
         # raise a TypeError
         self.check_not_optimized('(1,) < frozenset()')
-        self.check_not_optimized('(1, 2, 3) < frozenset("abc")')
+        self.check('(1, 2, 3) < frozenset("cba")',
+                   self.text_ast('(1, 2, 3) < frozenset(("a", "b", "c"))'))
 
     def test_methods(self):
         config = self.create_config()
         self.check('frozenset([1, 2, 3])',
                    self.text_ast('frozenset((1, 2, 3))'))
         self.check('list([1, 2, 3])',
-                   self.text_ast('list((1, 2, 3))'))
-        self.check('set([1, 2, 3])',
-                   self.text_ast('set((1, 2, 3))'))
+                   self.text_ast('[1, 2, 3]'))
+        if sys.version_info >= (2, 7):
+            self.check('set([1, 2, 3])',
+                       self.text_ast('{1, 2, 3}'))
+        else:
+            self.check('set([1, 2, 3])',
+                       self.text_ast('set((1, 2, 3))'))
 
     def test_no_builtin_funcs(self):
         config = self.create_config()