Skip to content

Commit 03aa4ca

Browse files
KonstantinPCManticore
Konstantin
authored andcommitted
Add check for swapping variables with tuples(#1922) (#1929)
1 parent dc2bc6a commit 03aa4ca

File tree

6 files changed

+94
-8
lines changed

6 files changed

+94
-8
lines changed

CONTRIBUTORS.txt

+1
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,4 @@ Order doesn't matter (not that much, at least ;)
160160

161161
* Tobias Hernstig: contributor
162162

163+
* Konstantin Manna: contributor

ChangeLog

+9-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Pylint's ChangeLog
44
What's New in Pylint 2.0?
55
=========================
66

7+
* Add a check `consider-swap-variables` for swapping variables with tuple unpacking
8+
9+
Close #1922
10+
711
* Don't crash on invalid strings when checking for `logging-format-interpolation`
812

913
Close #1944
@@ -141,7 +145,7 @@ Release date: 2017-12-15
141145

142146
Close #1713
143147

144-
* Fixing u'' string in superfluous-parens message
148+
* Fixing u'' string in superfluous-parens message
145149

146150
Close #1420
147151

@@ -163,7 +167,7 @@ Release date: 2017-12-15
163167
This may lead to args list getting modified if keyword argument's value
164168
is not provided in the function call assuming it will take default value
165169
provided in the definition.
166-
170+
167171
* The `invalid-name` check contains the name of the template that caused the failure
168172

169173
Close #1176
@@ -215,7 +219,7 @@ Release date: 2017-12-15
215219
* Added a new key-value pair in json output. The key is ``message-id``
216220
and the value is the message id.
217221
Close #1512
218-
222+
219223
* Added a new Python 3.0 check for raising a StopIteration inside a generator.
220224
The check about raising a StopIteration inside a generator is also valid if the exception
221225
raised inherit from StopIteration.
@@ -284,7 +288,7 @@ Release date: 2017-12-15
284288
Close #1681
285289

286290
* Fix ``line-too-long`` message deactivated by wrong disable directive.
287-
The directive ``disable=fixme`` doesn't deactivate anymore the emission
291+
The directive ``disable=fixme`` doesn't deactivate anymore the emission
288292
of ``line-too-long`` message for long commented lines.
289293
Close #1741
290294

@@ -295,7 +299,7 @@ Release date: 2017-12-15
295299
* Fix the wrong scope of the ``disable=`` directive after a commented line.
296300
For example when a ``disable=line-too-long`` directive is at the end of
297301
a long commented line, it no longer disables the emission of ``line-too-long``
298-
message for lines that follow.
302+
message for lines that follow.
299303
Close #1742
300304

301305
What's New in Pylint 1.7.1?

doc/whatsnew/2.0.rst

+22-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ New checkers
2020
deactivated by id instead of symbol.
2121
The use of symbol is more explicit and easier to remind.
2222

23+
* A new check was added, ``consider-swap-variables``.
24+
25+
This refactoring message is emitted when using a temporary variable in order
26+
to swap the values of two variables instead of the shorter, more idiomatic
27+
approach with tuple-unpacking.
28+
29+
Instead of a temporary variable, the one-line syntax with commas should be used.
30+
31+
See http://docs.python-guide.org/en/latest/writing/style/ or
32+
http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#swap-values
33+
for details.
34+
35+
.. code-block:: python
36+
37+
temp = a # the wrong way
38+
a = b
39+
b = temp
40+
41+
a, b = b, a # the right way
42+
2343
Other Changes
2444
=============
2545

@@ -29,15 +49,15 @@ Other Changes
2949
* Fix a false positive ``inconsistent-return-statements`` message when
3050
`while` loop are used.
3151

32-
* Fix emission of false positive ``no-member`` message for class with
52+
* Fix emission of false positive ``no-member`` message for class with
3353
"private" attributes whose name is mangled.
3454

3555
* Fix ``unused-argument`` false positives with overshadowed variable in dictionary comprehension.
3656

3757
* Fixing false positive ``inconsistent-return-statements`` when
3858
never returning functions are used (i.e such as sys.exit).
3959

40-
* Fix false positive ``inconsistent-return-statements`` message when a
60+
* Fix false positive ``inconsistent-return-statements`` message when a
4161
function is defined under an if statement.
4262

4363
* Fix false positive ``inconsistent-return-statements`` message by

pylint/checkers/refactoring.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ class RefactoringChecker(checkers.BaseTokenChecker):
130130
'at the end of function or method definition. This statement can safely be '
131131
'removed because Python will implicitly return None'
132132
),
133+
'R1712': ('Consider using tuple unpacking for swapping variables',
134+
'consider-swap-variables',
135+
'You do not have to use a temporary variable in order to '
136+
'swap variables. Using "tuple unpacking" to directly swap '
137+
'variables makes the intention more clear.'
138+
),
133139
}
134140
options = (('max-nested-blocks',
135141
{'default': 5, 'type': 'int', 'metavar': '<int>',
@@ -157,6 +163,7 @@ def _init(self):
157163
self._nested_blocks = []
158164
self._elifs = []
159165
self._nested_blocks_msg = None
166+
self._reported_swap_nodes = set()
160167

161168
def open(self):
162169
# do this in open since config not fully initialized in __init__
@@ -470,8 +477,35 @@ def visit_boolop(self, node):
470477
node=node,
471478
args=(duplicated_name, ', '.join(names)))
472479

473-
@utils.check_messages('simplify-boolean-expression', 'consider-using-ternary')
480+
@staticmethod
481+
def _is_simple_assignment(node):
482+
return (isinstance(node, astroid.Assign)
483+
and len(node.targets) == 1
484+
and isinstance(node.targets[0], astroid.node_classes.AssignName)
485+
and isinstance(node.value, astroid.node_classes.Name))
486+
487+
def _check_swap_variables(self, node):
488+
if not node.next_sibling() or not node.next_sibling().next_sibling():
489+
return
490+
assignments = [
491+
node, node.next_sibling(), node.next_sibling().next_sibling()
492+
]
493+
if not all(self._is_simple_assignment(node) for node in assignments):
494+
return
495+
if any(node in self._reported_swap_nodes for node in assignments):
496+
return
497+
left = [node.targets[0].name for node in assignments]
498+
right = [node.value.name for node in assignments]
499+
if left[0] == right[-1] and left[1:] == right[:-1]:
500+
self._reported_swap_nodes.update(assignments)
501+
message = 'consider-swap-variables'
502+
self.add_message(message, node=node)
503+
504+
@utils.check_messages('simplify-boolean-expression',
505+
'consider-using-ternary',
506+
'consider-swap-variables')
474507
def visit_assign(self, node):
508+
self._check_swap_variables(node)
475509
if self._is_and_or_ternary(node.value):
476510
cond, truth_value, false_value = self._and_or_ternary_arguments(node.value)
477511
elif self._is_seq_based_ternary(node.value):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# pylint: disable=missing-docstring,invalid-name,using-constant-test
2+
3+
a, b, c, d = 'a b c d'.split()
4+
5+
temp = a # [consider-swap-variables]
6+
a = b
7+
b = temp
8+
9+
temp = a # only simple swaps are reported
10+
a = b
11+
if True:
12+
b = a
13+
14+
temp = a # this is no swap
15+
a = b
16+
b = a
17+
18+
temp = a, b # complex swaps are ignored
19+
a, b = c, d
20+
c, d = temp
21+
22+
temp = a # [consider-swap-variables]
23+
a = b # longer swap circles are only reported once
24+
b = temp
25+
temp = a
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
consider-swap-variables:5::Consider using tuple unpacking for swapping variables
2+
consider-swap-variables:22::Consider using tuple unpacking for swapping variables

0 commit comments

Comments
 (0)