1
1
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2
2
# For details: https://github.com/PyCQA/pylint/blob/master/LICENSE
3
3
4
- from typing import cast
4
+ from typing import Optional , Union , cast
5
5
6
6
import astroid
7
7
8
8
from pylint import checkers , interfaces
9
9
from pylint .checkers import utils
10
10
11
11
12
+ def _check_if_dict_keys_used (
13
+ node : Union [astroid .For , astroid .Comprehension ]
14
+ ) -> Optional [str ]:
15
+ if not isinstance (node .iter , astroid .Call ):
16
+ # Is it a dictionary?
17
+ if not isinstance (node .iter , (astroid .Name , astroid .Attribute )):
18
+ return None
19
+ inferred = utils .safe_infer (node .iter )
20
+ if not isinstance (inferred , (astroid .Dict , astroid .DictComp )):
21
+ return None
22
+ iterating_object_name = node .iter .as_string ()
23
+
24
+ else :
25
+ # Is it a proper keys call?
26
+ if (
27
+ isinstance (node .iter .func , astroid .Name )
28
+ or node .iter .func .attrname != "keys"
29
+ ):
30
+ return None
31
+ inferred = utils .safe_infer (node .iter .func )
32
+ if not isinstance (inferred , (astroid .BoundMethod , astroid .Dict )):
33
+ return None
34
+ iterating_object_name = node .iter .as_string ().partition ("." )[0 ]
35
+ return iterating_object_name
36
+
37
+
12
38
class RecommendationChecker (checkers .BaseChecker ):
13
39
14
40
__implements__ = (interfaces .IAstroidChecker ,)
@@ -140,26 +166,9 @@ def _check_consider_using_dict_items(self, node: astroid.For) -> None:
140
166
# that the object which is iterated is used as a subscript in the
141
167
# body of the for.
142
168
143
- if not isinstance (node .iter , astroid .Call ):
144
- # Is it a dictionary?
145
- if not isinstance (node .iter , astroid .Name ):
146
- return
147
- inferred = utils .safe_infer (node .iter )
148
- if not isinstance (inferred , (astroid .Dict , astroid .DictComp )):
149
- return
150
- iterating_object_name = node .iter .as_string ()
151
-
152
- else :
153
- # Is it a proper keys call?
154
- if not (
155
- isinstance (node .iter .func , astroid .Attribute )
156
- and node .iter .func .attrname != "keys"
157
- ):
158
- return
159
- inferred = utils .safe_infer (node .iter .func )
160
- if not isinstance (inferred , (astroid .BoundMethod , astroid .Dict )):
161
- return
162
- iterating_object_name = node .iter .as_string ().partition ("." )[0 ]
169
+ iterating_object_name = _check_if_dict_keys_used (node )
170
+ if iterating_object_name is None :
171
+ return
163
172
164
173
# Verify that the body of the for loop uses a subscript
165
174
# with the object that was iterated. This uses some heuristics
@@ -168,7 +177,8 @@ def _check_consider_using_dict_items(self, node: astroid.For) -> None:
168
177
for child in node .body :
169
178
for subscript in child .nodes_of_class (astroid .Subscript ):
170
179
subscript = cast (astroid .Subscript , subscript )
171
- if not isinstance (subscript .value , astroid .Name ):
180
+
181
+ if not isinstance (subscript .value , (astroid .Name , astroid .Attribute )):
172
182
continue
173
183
174
184
value = subscript .slice
@@ -177,8 +187,9 @@ def _check_consider_using_dict_items(self, node: astroid.For) -> None:
177
187
if (
178
188
not isinstance (value , astroid .Name )
179
189
or value .name != node .target .name
180
- or iterating_object_name != subscript .value .name
190
+ or iterating_object_name != subscript .value .as_string ()
181
191
):
192
+
182
193
continue
183
194
last_definition_lineno = value .lookup (value .name )[1 ][- 1 ].lineno
184
195
if last_definition_lineno > node .lineno :
@@ -187,5 +198,39 @@ def _check_consider_using_dict_items(self, node: astroid.For) -> None:
187
198
# to get the line number where the iterating object was last
188
199
# defined and compare that to the for loop's line number
189
200
continue
201
+ if (
202
+ isinstance (subscript .parent , astroid .Assign )
203
+ and subscript in subscript .parent .targets
204
+ ):
205
+ # Ignore this subscript if it is the target of an assignment
206
+ continue
207
+
208
+ self .add_message ("consider-using-dict-items" , node = node )
209
+ return
210
+
211
+ @utils .check_messages ("consider-using-dict-items" )
212
+ def visit_comprehension (self , node : astroid .Comprehension ) -> None :
213
+ iterating_object_name = _check_if_dict_keys_used (node )
214
+ if iterating_object_name is None :
215
+ return
216
+
217
+ children = list (node .parent .get_children ())
218
+ if node .ifs :
219
+ children += node .ifs
220
+ for child in children :
221
+ for subscript in child .nodes_of_class (astroid .Subscript ):
222
+ subscript = cast (astroid .Subscript , subscript )
223
+ if not isinstance (subscript .value , (astroid .Name , astroid .Attribute )):
224
+ continue
225
+
226
+ value = subscript .slice
227
+ if isinstance (value , astroid .Index ):
228
+ value = value .value
229
+ if (
230
+ not isinstance (value , astroid .Name )
231
+ or value .name != node .target .name
232
+ or iterating_object_name != subscript .value .as_string ()
233
+ ):
234
+ continue
190
235
self .add_message ("consider-using-dict-items" , node = node )
191
236
return
0 commit comments