Skip to content

Commit a7302db

Browse files
authored
Treat typing.NamedTuple self as a sequence (#5346)
* Treat `typing.NamedTuple` self as a sequence Closes #5312 * Support emmitting `unbalanced-tuple-unpacking` with `typing.NamedTuple` * Add typehint and default to `None` * Use Marc's suggested improvements
1 parent 7873b77 commit a7302db

File tree

6 files changed

+63
-17
lines changed

6 files changed

+63
-17
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Release date: TBA
1818

1919
Closes #4716
2020

21+
* Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``.
22+
23+
Closes #5312
24+
2125
* Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables
2226
or ``not in`` checks
2327

pylint/checkers/variables.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2130,9 +2130,17 @@ def _check_unpacking(self, inferred, node, targets):
21302130
):
21312131
# Variable-length argument, we can't determine the length.
21322132
return
2133+
2134+
# Attempt to check unpacking is properly balanced
2135+
values: Optional[List] = None
21332136
if isinstance(inferred, (nodes.Tuple, nodes.List)):
2134-
# attempt to check unpacking is properly balanced
21352137
values = inferred.itered()
2138+
elif isinstance(inferred, astroid.Instance) and any(
2139+
ancestor.qname() == "typing.NamedTuple" for ancestor in inferred.ancestors()
2140+
):
2141+
values = [i for i in inferred.values() if isinstance(i, nodes.AssignName)]
2142+
2143+
if values:
21362144
if len(targets) != len(values):
21372145
# Check if we have starred nodes.
21382146
if any(isinstance(target, nodes.Starred) for target in targets):

tests/functional/u/unbalanced_tuple_unpacking.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Check possible unbalanced tuple unpacking """
22
from __future__ import absolute_import
3+
from typing import NamedTuple
34
from functional.u.unpacking import unpack
45

5-
# pylint: disable=using-constant-test, useless-object-inheritance,import-outside-toplevel
6+
# pylint: disable=missing-class-docstring, missing-function-docstring, using-constant-test, useless-object-inheritance,import-outside-toplevel
67

78
def do_stuff():
89
"""This is not right."""
@@ -106,3 +107,24 @@ def test_issue_559():
106107
from ctypes import c_int
107108
root_x, root_y, win_x, win_y = [c_int()] * 4
108109
return root_x, root_y, win_x, win_y
110+
111+
112+
class MyClass(NamedTuple):
113+
first: float
114+
second: float
115+
third: float = 1.0
116+
117+
def my_sum(self):
118+
"""Unpack 3 variables"""
119+
first, second, third = self
120+
return first + second + third
121+
122+
def sum_unpack_3_into_4(self):
123+
"""Attempt to unpack 3 variables into 4"""
124+
first, second, third, fourth = self # [unbalanced-tuple-unpacking]
125+
return first + second + third + fourth
126+
127+
def sum_unpack_3_into_2(self):
128+
"""Attempt to unpack 3 variables into 2"""
129+
first, second = self # [unbalanced-tuple-unpacking]
130+
return first + second
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
unbalanced-tuple-unpacking:9:4:9:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
2-
unbalanced-tuple-unpacking:14:4:14:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
3-
unbalanced-tuple-unpacking:19:4:19:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
4-
unbalanced-tuple-unpacking:69:4:69:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
5-
unbalanced-tuple-unpacking:81:8:81:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
1+
unbalanced-tuple-unpacking:10:4:10:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
2+
unbalanced-tuple-unpacking:15:4:15:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
3+
unbalanced-tuple-unpacking:20:4:20:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
4+
unbalanced-tuple-unpacking:70:4:70:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
5+
unbalanced-tuple-unpacking:82:8:82:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
6+
unbalanced-tuple-unpacking:124:8:124:43:MyClass.sum_unpack_3_into_4:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 4 label(s), right side has 3 value(s)":UNDEFINED
7+
unbalanced-tuple-unpacking:129:8:129:28:MyClass.sum_unpack_3_into_2:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 2 label(s), right side has 3 value(s)":UNDEFINED

tests/functional/u/unpacking_non_sequence.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# pylint: disable=using-constant-test, no-init, missing-docstring, wrong-import-order,wrong-import-position,no-else-return, useless-object-inheritance
55
from os import rename as nonseq_func
66
from functional.u.unpacking import nonseq
7+
from typing import NamedTuple
78

89
__revision__ = 0
910

@@ -137,3 +138,12 @@ def flow_control_unpacking(var=None):
137138
var0, var1 = var
138139
return var0, var1
139140
return None
141+
142+
143+
class MyClass(NamedTuple):
144+
x: float
145+
y: float
146+
147+
def sum(self):
148+
x, y = self
149+
return x + y
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
unpacking-non-sequence:77:0:77:15::Attempting to unpack a non-sequence defined at line 74:UNDEFINED
2-
unpacking-non-sequence:78:0:78:17::Attempting to unpack a non-sequence:UNDEFINED
3-
unpacking-non-sequence:79:0:79:11::Attempting to unpack a non-sequence None:UNDEFINED
4-
unpacking-non-sequence:80:0:80:8::Attempting to unpack a non-sequence 1:UNDEFINED
5-
unpacking-non-sequence:81:0:81:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking:UNDEFINED
6-
unpacking-non-sequence:82:0:82:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking:UNDEFINED
7-
unpacking-non-sequence:83:0:83:18::Attempting to unpack a non-sequence:UNDEFINED
8-
unpacking-non-sequence:98:8:98:33:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 74:UNDEFINED
9-
unpacking-non-sequence:99:8:99:35:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED
10-
unpacking-non-sequence:100:8:100:31:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED
1+
unpacking-non-sequence:78:0:78:15::Attempting to unpack a non-sequence defined at line 75:UNDEFINED
2+
unpacking-non-sequence:79:0:79:17::Attempting to unpack a non-sequence:UNDEFINED
3+
unpacking-non-sequence:80:0:80:11::Attempting to unpack a non-sequence None:UNDEFINED
4+
unpacking-non-sequence:81:0:81:8::Attempting to unpack a non-sequence 1:UNDEFINED
5+
unpacking-non-sequence:82:0:82:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking:UNDEFINED
6+
unpacking-non-sequence:83:0:83:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking:UNDEFINED
7+
unpacking-non-sequence:84:0:84:18::Attempting to unpack a non-sequence:UNDEFINED
8+
unpacking-non-sequence:99:8:99:33:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 75:UNDEFINED
9+
unpacking-non-sequence:100:8:100:35:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED
10+
unpacking-non-sequence:101:8:101:31:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED

0 commit comments

Comments
 (0)