This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Title: Change bare AttributeError messages to be more informative
Type: enhancement Stage: resolved
Components: Interpreter Core Versions: Python 3.6
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: abarry, iritkatriel, r.david.murray, rhettinger, xiang.zhang, ztane
Priority: normal Keywords: patch

Created on 2016-08-21 16:27 by abarry, last changed 2022-04-11 14:58 by admin. This issue is now closed.

File name Uploaded Description Edit
change_attr_error_messages_1.patch abarry, 2016-08-21 16:27 review
Messages (5)
msg273300 - (view) Author: Anilyka Barry (abarry) * (Python triager) Date: 2016-08-21 16:27
Attached patch changes bare attribute errors to add a useful error message. As such, `raise AttributeError` and `raise AttributeError(None)` (but not `raise AttributeError('')` for example) inside any of the attribute lookup or descriptor methods (__getattribute__, __getattr__, __setattr__, __delattr__, __get__, __set__, and __delete__). This is a followup to #27794.

As such, the following is now much more useful and less boilerplate:

class A:
    _dynamic_names = {}
    def __getattr__(self, name):
        if name in self._dynamic_names:
            return self._dynamic_names[name]
        raise AttributeError

Then, doing the following:


Will result in a `AttributeError("'A' object has no attribute 'spam'")`, while keeping the original traceback. The patch is pretty much complete, but I have a few concerns:

- Performance hit, especially on something that's used virtually everywhere and everytime (attribute access); although PEP 487's __set_name__ lookup already hurts performance slightly. The few micro-benchmarks I ran weren't very significant, but there is a small drop in performance nonetheless.
- Four new PyErr_* API functions, one which isn't used in the current patch, but since it's 3 lines I decided to leave it in anyway.
- I don't carry over the cause or context of the raised exception to the new one. Keeping the traceback around is pretty trivial, since it has its own slot in the thread state struct, but since not all exceptions are actual exceptions at the C level (and the new exceptions I raise aren't either, at the C level), I don't know how to carry over context or cause.

Oh, and exceptions set at the C level with e.g. `PyErr_SetNone` are also properly handled that way; this would fix #27794 as a side-effect, but a few lines will need to be changed (in Objects/descrobject.c) for this to work. Such changes are not included with the patch.

Thoughts, concerns, opinions, ideas? Maybe something I didn't think of?
msg273310 - (view) Author: Xiang Zhang (xiang.zhang) * (Python committer) Date: 2016-08-21 18:37
-1. I don't think this is equal to issue27794. I don't hope when I use `raise AttributeError`, Python hides some magic and sets a message I totally don't want. If you do want a good message, why not passing the message when raising?
msg273323 - (view) Author: Anilyka Barry (abarry) * (Python triager) Date: 2016-08-22 01:12
Thanks for your comments Xiang. Yes, it's not equal to #27794, but it's one of the multiple ways to fix it, so I made a new issue about it.

The rationale between a bare `raise AttributeError` being changed is the idea that it carries no information, other than the fact there was an attribute error (it doesn't even mean the attribute doesn't exist, as it may). Also note that this only affects bare AttributeErrors inside of the magic methods dedicated to attribute lookup (and the descriptor machinery). In a regular function (or even just doing `foo.__getattribute__(bar)`), `raise AttributeError` does nothing special.

The argument "If you do want a good message, why not passing the message when raising?" is one I hear a lot when there's a suggestion to add a more convenient way to do something that can already be done. I've seen a lot of code (including code in the core Python distribution, both in Python and C) which lazily does `raise AttributeError(name)`. It's only half the information, and these uninformative error messages are left completely untouched by my patch. The patch will help reduce boilerplate in some code. I'm personally one of those people who go ahead and actually use the built-in error message in my own code, but if I can reduce the boilerplate needed while maintaining the same level of information and usefulness for debugging, I'll be happy.

On the other hand, the argument that Python hides away some magic is a valid concern. Python already has some magic in various places; the point is to determine whether this kind of magic is acceptable for Python and its users. I honestly don't know; I don't think the magic is too strong, but at the same time it's likely to surprise newcomers. We'll see what others think :)
msg389265 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2021-03-21 23:53

As Anilyka wrote: "it (AttributeError) doesn't even mean the attribute doesn't exist, as it may".

So it's not safe to fill in a missing message based on the assumption that we know what it should be.
msg392898 - (view) Author: Anilyka Barry (abarry) * (Python triager) Date: 2021-05-04 14:04
I'd forgotten that I did that. Looking back on it, this is indeed not a good change. Since there hasn't been any traction on in for 5 years, I think it's safe to say it's not something people want.
Date User Action Args
2022-04-11 14:58:35adminsetgithub: 72010
2021-05-04 14:04:03abarrysetstatus: open -> closed
resolution: rejected
messages: + msg392898

stage: patch review -> resolved
2021-03-21 23:53:10iritkatrielsetnosy: + iritkatriel
messages: + msg389265
2016-08-22 01:12:59abarrysetmessages: + msg273323
2016-08-21 18:37:37xiang.zhangsetmessages: + msg273310
2016-08-21 16:48:11xiang.zhangsetnosy: + rhettinger
2016-08-21 16:27:48abarrycreate