New submission from Géry <gery.o...@gmail.com>: Compare the `AttributeError` messages in this interactive Python session:
```python >>> class A: ... y = 0 ... __slots__ = ('z',) ... >>> A().x […] AttributeError: 'A' object has no attribute 'x' >>> A().x = 1 […] AttributeError: 'A' object has no attribute 'x' >>> del A().x […] AttributeError: 'A' object has no attribute 'x' >>> A().y 0 >>> A().y = 2 […] AttributeError: 'A' object attribute 'y' is read-only >>> del A().y […] AttributeError: 'A' object attribute 'y' is read-only >>> A().z […] AttributeError: z >>> A().z = 3 >>> del A().z […] AttributeError: z ``` with the `AttributeError` messages in that one: ```python >>> class B: pass ... >>> B().x […] AttributeError: 'B' object has no attribute 'x' >>> B().x = 1 >>> del B().x […] AttributeError: x ``` The message `AttributeError: x` from `del B().x` does not feel right. I expect this message to be the same as the message `AttributeError: 'B' object has no attribute 'x'` from `B().x`, since in both cases the object `B()` has no attribute `'x'`. I have checked on PyPy 7.3.3 (Python 3.7.9) and it uses the expected message `AttributeError: 'B' object has no attribute 'x'` from `B().x` for `del B().x`. So this confirms my initial suspicion. ---- In CPython, the `AttributeError` message for attribute retrieval is implemented [here](https://github.com/python/cpython/blob/v3.9.4/Objects/object.c#L1266-L1270) (except for [slot retrieval](https://github.com/python/cpython/blob/v3.9.4/Python/structmember.c#L70-L75)): ```c if (!suppress) { PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); } ``` And the `AttributeError` messages for attribute assignment and deletion are implemented [here](https://github.com/python/cpython/blob/v3.9.4/Objects/object.c#L1324-L1350) (except for [slot deletion](https://github.com/python/cpython/blob/v3.9.4/Python/structmember.c#L112-L118)): ```c if (dict == NULL) { dictptr = _PyObject_GetDictPtr(obj); if (dictptr == NULL) { if (descr == NULL) { PyErr_Format(PyExc_AttributeError, "'%.100s' object has no attribute '%U'", tp->tp_name, name); } else { PyErr_Format(PyExc_AttributeError, "'%.50s' object attribute '%U' is read-only", tp->tp_name, name); } goto done; } res = _PyObjectDict_SetItem(tp, dictptr, name, value); } else { Py_INCREF(dict); if (value == NULL) res = PyDict_DelItem(dict, name); else res = PyDict_SetItem(dict, name, value); Py_DECREF(dict); } if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) PyErr_SetObject(PyExc_AttributeError, name); ``` So it is the last line `PyErr_SetObject(PyExc_AttributeError, name);` that would be updated. Note that `_PyObjectDict_SetItem` delegates to `PyDict_DelItem` (if `value` is `NULL`) or `PyDict_SetItem` (if `value` is not `NULL`), and that only `PyDict_DelItem` can [set an exception](https://github.com/python/cpython/blob/v3.9.4/Objects/dictobject.c#L1655-L1657) `PyExc_KeyError`, which is then translated to an exception `PyExc_AttributeError` in the last line. ---------- components: Interpreter Core messages: 391140 nosy: maggyero priority: normal severity: normal status: open title: Fix the AttributeError message for deletion of a missing attribute type: enhancement versions: Python 3.10, Python 3.6, Python 3.7, Python 3.8, Python 3.9 _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue43857> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com