New submission from Géry <[email protected]>:
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 <[email protected]>
<https://bugs.python.org/issue43857>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com