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

Reply via email to