give [deprecated-lambda] when a map/filter of a lambda could be a comprehension. Closes #120657

authorMartin Pool <mbp@google.com>
changeset4af14e854aeb
branchdefault
phasepublic
hiddenno
parent revision#9ee22d817eda use load_module_from_file rather than relative import broken in python 3.3. Closes #110213
child revision#a6d26ab926f1 changelog cleanup
files modified by this revision
ChangeLog
checkers/base.py
test/input/func_deprecated_lambda.py
test/messages/func_deprecated_lambda.txt
# HG changeset patch
# User Martin Pool <mbp@google.com>
# Date 1361917051 -3600
# Tue Feb 26 23:17:31 2013 +0100
# Node ID 4af14e854aebda909a85cfc47d58732e3fe96ff6
# Parent 9ee22d817eda42874cbb99e995e1cf4a66a3d0d8
give [deprecated-lambda] when a map/filter of a lambda could be a comprehension. Closes #120657

diff --git a/ChangeLog b/ChangeLog
@@ -22,10 +22,13 @@
1        and useless suppression pragmas. (patch by Torsten Marek)
2 
3      * #112728: add warning E0604 for non-string objects in __all__
4        (patch by Torsten Marek)
5 
6 +    * #120657: add warning W0110/deprecated-lambda when a map/filter
7 +      of a lambda could be a comprehension (patch by Martin Pool)
8 +
9      * #113231: logging checker now looks at instances of Logger classes
10        in addition to the base logging module. (patch by Mike Bryant)
11 
12      * #111799: don't warn about octal escape sequence, warn about \o
13        which is not octal in Python (patch by Martin Pool)
diff --git a/checkers/base.py b/checkers/base.py
@@ -21,11 +21,16 @@
14  from logilab.astng import are_exclusive
15 
16  from pylint.interfaces import IASTNGChecker
17  from pylint.reporters import diff_string
18  from pylint.checkers import BaseChecker, EmptyReport
19 -from pylint.checkers.utils import check_messages, clobber_in_except, is_inside_except
20 +from pylint.checkers.utils import (
21 +    check_messages,
22 +    clobber_in_except,
23 +    is_inside_except,
24 +    safe_infer,
25 +    )
26 
27 
28  import re
29 
30  # regex for class/function/variable/constant name
@@ -796,20 +801,51 @@
31  class PassChecker(_BasicChecker):
32      """check is the pass statement is really necessary"""
33      msgs = {'W0107': ('Unnecessary pass statement',
34                        'unnecessary-pass',
35                        'Used when a "pass" statement that can be avoided is '
36 -                      'encountered.)'),
37 +                      'encountered.'),
38              }
39 
40      def visit_pass(self, node):
41          if len(node.parent.child_sequence(node)) > 1:
42              self.add_message('W0107', node=node)
43 
44 
45 +class LambdaForComprehensionChecker(_BasicChecker):
46 +    """check for using a lambda where a comprehension would do.
47 +
48 +    See <http://www.artima.com/weblogs/viewpost.jsp?thread=98196>
49 +    where GvR says comprehensions would be clearer.
50 +    """
51 +
52 +    msgs = {'W0110': ('map/filter on lambda could be replaced by comprehension',
53 +                      'deprecated-lambda',
54 +                      'Used when a lambda is the first argument to "map" or '
55 +                      '"filter". It could be clearer as a list '
56 +                      'comprehension or generator expression.'),
57 +            }
58 +
59 +    @check_messages('W0110')
60 +    def visit_callfunc(self, node):
61 +        """visit a CallFunc node, check if map or filter are called with a
62 +        lambda
63 +        """
64 +        if not node.args:
65 +            return
66 +        if not isinstance(node.args[0], astng.Lambda):
67 +            return
68 +        infered = safe_infer(node.func)
69 +        if (infered
70 +            and infered.parent.name == '__builtin__'
71 +            and infered.name in ['map', 'filter']):
72 +            self.add_message('W0110', node=node)
73 +
74 +
75  def register(linter):
76      """required method to auto register this checker"""
77      linter.register_checker(BasicErrorChecker(linter))
78      linter.register_checker(BasicChecker(linter))
79      linter.register_checker(NameChecker(linter))
80      linter.register_checker(DocStringChecker(linter))
81      linter.register_checker(PassChecker(linter))
82 +    linter.register_checker(LambdaForComprehensionChecker(linter))
diff --git a/test/input/func_deprecated_lambda.py b/test/input/func_deprecated_lambda.py
@@ -0,0 +1,22 @@
83 +# pylint: disable=missing-docstring,bad-builtin,invalid-name
84 +__revision__ = "$Id$"
85 +
86 +# Don't do this, use a comprehension instead.
87 +assert map(lambda x: x*2, [1, 2, 3]) == [2, 4, 6]
88 +
89 +assert filter(lambda x: x != 1, [1, 2, 3]) == [2, 3]
90 +
91 +# It's still ok to use map and filter with anything but an inline lambda.
92 +double = lambda x: x * 2
93 +assert map(double, [1, 2, 3]) == [2, 4, 6]
94 +
95 +# It's also ok to pass lambdas to other functions.
96 +assert reduce(lambda x, y: x * y, [1, 2, 3, 4]) == 24
97 +
98 +# Or to a undefined function or one with varargs
99 +def f(*a):
100 +    return len(a)
101 +
102 +f(lambda x, y: x + y, [1, 2, 3])
103 +
104 +undefined_function(lambda: 2)  # pylint: disable=undefined-variable
diff --git a/test/messages/func_deprecated_lambda.txt b/test/messages/func_deprecated_lambda.txt
@@ -0,0 +1,2 @@
105 +W:  5: map/filter on lambda could be replaced by comprehension
106 +W:  7: map/filter on lambda could be replaced by comprehension