Skip to content

Commit 7b79386

Browse files
Fix assigning-non-slot false positive with setattr (#5457)
* Fix assigning-non-slot false positive with setattr Previously, if a class was slotted and overrode `__setattr__`, `assigning-non-slot` would be issued when assigning to attributes. With `__setattr__` defined, we cannot infer if it is an error to assign to an attribute, so we suppress the error. Fix #3793 Co-authored-by: Pierre Sassoulas <[email protected]>
1 parent fe547fb commit 7b79386

File tree

5 files changed

+47
-0
lines changed

5 files changed

+47
-0
lines changed

CONTRIBUTORS.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ contributors:
585585

586586
* Felix von Drigalski (felixvd): contributor
587587

588+
* Jake Lishman (jakelishman): contributor
589+
588590
* Philipp Albrecht (pylbrecht): contributor
589591

590592
* Allan Chandler (allanc65): contributor

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ Release date: TBA
1616

1717
Closes #85, #2615
1818

19+
* Fixed a false positive for ``assigning-non-slot`` when the slotted class
20+
defined ``__setattr__``.
21+
22+
Closes #3793
23+
1924
* ``used-before-assignment`` now assumes that assignments in except blocks
2025
may not have occurred and warns accordingly.
2126

doc/whatsnew/2.13.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ Other Changes
5151

5252
Closes #85, #2615
5353

54+
* Fix a false positive for ``assigning-non-slot`` when the slotted class
55+
defined ``__setattr__``.
56+
57+
Closes #3793
58+
5459
* ``used-before-assignment`` now assumes that assignments in except blocks
5560
may not have occurred and warns accordingly.
5661

pylint/checkers/classes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,14 @@ def _check_in_slots(self, node):
14871487
return
14881488
if "__slots__" not in klass.locals or not klass.newstyle:
14891489
return
1490+
# If `__setattr__` is defined on the class, then we can't reason about
1491+
# what will happen when assigning to an attribute.
1492+
if any(
1493+
base.locals.get("__setattr__")
1494+
for base in klass.mro()
1495+
if base.qname() != "builtins.object"
1496+
):
1497+
return
14901498

14911499
# If 'typing.Generic' is a base of bases of klass, the cached version
14921500
# of 'slots()' might have been evaluated incorrectly, thus deleted cache entry.

tests/functional/a/assign/assigning_non_slot.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,30 @@ class Cls(Generic[TYPE]):
173173

174174
def __init__(self, value):
175175
self.value = value
176+
177+
178+
class ClassDefiningSetattr(object):
179+
__slots__ = ["foobar"]
180+
181+
def __init__(self):
182+
self.foobar = {}
183+
184+
def __setattr__(self, name, value):
185+
if name == "foobar":
186+
super().__setattr__(name, value)
187+
else:
188+
self.foobar[name] = value
189+
190+
191+
class ClassWithParentDefiningSetattr(ClassDefiningSetattr):
192+
__slots__ = []
193+
194+
195+
def dont_emit_for_defined_setattr():
196+
inst = ClassDefiningSetattr()
197+
# This should not emit because we can't reason about what happens with
198+
# classes defining __setattr__
199+
inst.non_existent = "non-existent"
200+
201+
child = ClassWithParentDefiningSetattr()
202+
child.non_existent = "non-existent"

0 commit comments

Comments
 (0)