Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cdeb1a6

Browse filesBrowse files
authored
gh-96663: Add a better error message for __dict__-less classes setattr (#103232)
1 parent 41ca164 commit cdeb1a6
Copy full SHA for cdeb1a6

File tree

Expand file treeCollapse file tree

4 files changed

+32
-5
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+32
-5
lines changed

‎Lib/test/test_class.py

Copy file name to clipboardExpand all lines: Lib/test/test_class.py
+17-1Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,14 @@ class A:
641641
class B:
642642
y = 0
643643
__slots__ = ('z',)
644+
class C:
645+
__slots__ = ("y",)
646+
647+
def __setattr__(self, name, value) -> None:
648+
if name == "z":
649+
super().__setattr__("y", 1)
650+
else:
651+
super().__setattr__(name, value)
644652

645653
error_msg = "'A' object has no attribute 'x'"
646654
with self.assertRaisesRegex(AttributeError, error_msg):
@@ -653,8 +661,16 @@ class B:
653661
B().x
654662
with self.assertRaisesRegex(AttributeError, error_msg):
655663
del B().x
656-
with self.assertRaisesRegex(AttributeError, error_msg):
664+
with self.assertRaisesRegex(
665+
AttributeError,
666+
"'B' object has no attribute 'x' and no __dict__ for setting new attributes"
667+
):
657668
B().x = 0
669+
with self.assertRaisesRegex(
670+
AttributeError,
671+
"'C' object has no attribute 'x'"
672+
):
673+
C().x = 0
658674

659675
error_msg = "'B' object attribute 'y' is read-only"
660676
with self.assertRaisesRegex(AttributeError, error_msg):

‎Lib/test/test_descrtut.py

Copy file name to clipboardExpand all lines: Lib/test/test_descrtut.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def merge(self, other):
139139
>>> a.x1 = 1
140140
Traceback (most recent call last):
141141
File "<stdin>", line 1, in ?
142-
AttributeError: 'defaultdict2' object has no attribute 'x1'
142+
AttributeError: 'defaultdict2' object has no attribute 'x1' and no __dict__ for setting new attributes
143143
>>>
144144
145145
"""
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a better, more introspect-able error message when setting attributes on classes without a ``__dict__`` and no slot member for the attribute.

‎Objects/object.c

Copy file name to clipboardExpand all lines: Objects/object.c
+13-3Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,9 +1576,18 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
15761576
}
15771577
if (dictptr == NULL) {
15781578
if (descr == NULL) {
1579-
PyErr_Format(PyExc_AttributeError,
1580-
"'%.100s' object has no attribute '%U'",
1581-
tp->tp_name, name);
1579+
if (tp->tp_setattro == PyObject_GenericSetAttr) {
1580+
PyErr_Format(PyExc_AttributeError,
1581+
"'%.100s' object has no attribute '%U' and no "
1582+
"__dict__ for setting new attributes",
1583+
tp->tp_name, name);
1584+
}
1585+
else {
1586+
PyErr_Format(PyExc_AttributeError,
1587+
"'%.100s' object has no attribute '%U'",
1588+
tp->tp_name, name);
1589+
}
1590+
set_attribute_error_context(obj, name);
15821591
}
15831592
else {
15841593
PyErr_Format(PyExc_AttributeError,
@@ -1611,6 +1620,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
16111620
"'%.100s' object has no attribute '%U'",
16121621
tp->tp_name, name);
16131622
}
1623+
set_attribute_error_context(obj, name);
16141624
}
16151625
done:
16161626
Py_XDECREF(descr);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.