コード例 #1
0
    def _check_consider_using_enumerate(self, node: nodes.For) -> None:
        """Emit a convention whenever range and len are used for indexing."""
        # Verify that we have a `range([start], len(...), [stop])` call and
        # that the object which is iterated is used as a subscript in the
        # body of the for.

        # Is it a proper range call?
        if not isinstance(node.iter, nodes.Call):
            return
        if not self._is_builtin(node.iter.func, "range"):
            return
        if not node.iter.args:
            return
        is_constant_zero = (isinstance(node.iter.args[0], nodes.Const)
                            and node.iter.args[0].value == 0)
        if len(node.iter.args) == 2 and not is_constant_zero:
            return
        if len(node.iter.args) > 2:
            return

        # Is it a proper len call?
        if not isinstance(node.iter.args[-1], nodes.Call):
            return
        second_func = node.iter.args[-1].func
        if not self._is_builtin(second_func, "len"):
            return
        len_args = node.iter.args[-1].args
        if not len_args or len(len_args) != 1:
            return
        iterating_object = len_args[0]
        if isinstance(iterating_object, nodes.Name):
            expected_subscript_val_type = nodes.Name
        elif isinstance(iterating_object, nodes.Attribute):
            expected_subscript_val_type = nodes.Attribute
        else:
            return
        # If we're defining __iter__ on self, enumerate won't work
        scope = node.scope()
        if (isinstance(iterating_object, nodes.Name)
                and iterating_object.name == "self"
                and scope.name == "__iter__"):
            return

        # Verify that the body of the for loop uses a subscript
        # with the object that was iterated. This uses some heuristics
        # in order to make sure that the same object is used in the
        # for body.
        for child in node.body:
            for subscript in child.nodes_of_class(nodes.Subscript):
                if not isinstance(subscript.value,
                                  expected_subscript_val_type):
                    continue

                value = subscript.slice
                if not isinstance(value, nodes.Name):
                    continue
                if subscript.value.scope() != node.scope():
                    # Ignore this subscript if it's not in the same
                    # scope. This means that in the body of the for
                    # loop, another scope was created, where the same
                    # name for the iterating object was used.
                    continue
                if value.name == node.target.name and (
                        isinstance(subscript.value, nodes.Name)
                        and iterating_object.name == subscript.value.name
                        or isinstance(subscript.value, nodes.Attribute) and
                        iterating_object.attrname == subscript.value.attrname):
                    self.add_message("consider-using-enumerate", node=node)
                    return