Provide an option to disable name suggestions when raising AttributeError/NameError

Given this example where the bar attribute has been deprecated in favor of new_bar:

class Foo:
    def __init__(self):
        self._bar = "other_bar"
        self.new_bar = "new_bar"

    def __getattr__(self, name):
        if name == "bar":
            raise AttributeError("Use 'new_bar' instead.")
        return super().__getattribute__(name)

Foo().bar

Since Python 3.10, there are name suggestions, and the resulting error message will be:

AttributeError: Use 'new_bar' instead.. Did you mean: '_bar'?

This is very similar to this suggestion. However, in this case, the AttributeError is raised manually in the code and the feature that followed this suggestion no longer applies.

Moreover, the error message provided manually may conflict with the name suggestion. For example, with NumPy 2.0:

>>> import numpy
>>> numpy.float_
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/arnaud-ma/.pyenv/versions/3.12.6/lib/python3.12/site-packages/numpy/__init__.py", line 397, in __getattr__
    raise AttributeError(
AttributeError: `np.float_` was removed in the NumPy 2.0 release. Use `np.float64` instead.. Did you mean: 'float16'?

The undocumented workaround is to replace raise AttributeError(msg) with raise AttributeError(msg, name=None). However, this is not documented at all, and it could break at any moment. The only thing that is said in the doc is:

The name and obj attributes can be set using keyword-only arguments to the constructor. When set they represent the name of the attribute that was attempted to be accessed and the object that was accessed for said attribute, respectively.

Possible solutions include:

  • Providing a way to disable name suggestions, or at least documenting the fact that setting the name attribute to None disables name suggestions.
  • Do we really need name suggestions when raising an AttributeError/NameError manually with a specific message? The message should already contain all the necessary information about the error. Moreover, the author of the message is likely unaware that something will be appended afterward. In my opinion, this behavior should be disabled by default.
9 Likes

The issue is that newer versions of python[1] search for a similarly-named attribute and appends that suggestion to the message.

It seems like disabling the name suggestion is already possible–a PR to the documentation seems like the right step forward?

More concretely for this example: if bar is deprecated, it seems better to have a DeprecationWarning and let the code keep working, and then after some time it’s removed and you can just let it raise a normal error.

Even if you want to raise an error, I think it’d better off creating a property for bar rather than overwriting __getattr__, and perhaps using warnings.deprecated (if you can rely on python 3.13)


  1. not as early as 3.10, in my test, but 3.12 does this ↩︎

The easiest way to figure this out is to just run the example given by OP. Because contrary to what you are suggesting, if AttributeError is raised from within __getattr__, name and obj are filled in automatically:

try:
    Foo().bar
except AttributeError as ae:
    print(f"AE {ae.name} {ae.obj}") # AE bar <__main__.Foo object at 0x0000018AB7630410>

IMO documenting that name=None prevents the suggestion is the most sensible resolution. Contrary to OP, I doubt that most such explicitly raised error messages already contain a suggestion, so I think giving a hint should be the default.

2 Likes

…which is precisely the scenario raised in the OP

1 Like

Can I suggest you take a python3.13+ interpreter and just play around with the code given in OP?

Tested on Python 3.10 and above:

import sys
print(sys.version)

class Foo:
    def __init__(self):
        self._bar = "other_bar"
        self.new_bar = "new_bar"

    def __getattr__(self, name):
        if name == "bar":
            raise AttributeError("Use 'new_bar' instead.")
        return super().__getattr__(name)

try:
    Foo().bar
except AttributeError as ae:
    print(ae)
    
Foo().bar

Result:

Use 'new_bar' instead.
Traceback (most recent call last):
  File "...", line 19, in <module>
    Foo().bar
  File "...", line 11, in __getattr__
    raise AttributeError("Use 'new_bar' instead.")
AttributeError: Use 'new_bar' instead.. Did you mean: '_bar'?

So, if we use a try/except block to print the exception, the suggested name is not appended to the message.

Oh, an interesting wrinkle–I thought this didn’t occur in 3.10 because I tested in a jupyter notebook and the message wasn’t there. It must be catching the exception and printing it out, which doesn’t include the suggestion.

This is a bit of a caveat to all the builtin exception types with custom __str__() methods. I had a similar (albeit lesser) issue with a numpy-ified dict-like library I babysit where I wanted to raise a KeyError with a custom message rather than just the missing key but KeyError() insisted on wrapping the message in quotes.

My suggestion to numpy would just be to subclass the exception and set __str__() back to the default:

class AttributeRemovedError(AttributeError):
    __str__ = Exception.__str__
3 Likes

The issue is that name suggestions are not part of str(error). They are added after, when the whole traceback is rendered. Changing things on the exception side itself doesn’t work here.

… But it does work already? If you pass name=None to the AttributeError constructor, the extra message vanishes. IMO, this should documented as the correct solution, essentially telling the interpreter and the user “the error message is standalone, no further introspection needed”.

I agree with that. Though it seems it is only an implementation detail in the source code for the moment. So adding it to the documentation would mean adding it officially as a feature, even if the source code changes

Restating the issue. The AttributeError API was not stable between Python 3.9 and Python 3.10:

python3.9 foo.py

Traceback (most recent call last):
  File "/tmp/foo.py", line 11, in <module>
    Foo().bar
  File "/tmp/foo.py", line 8, in __getattr__
    raise AttributeError("Use 'new_bar' instead.")
AttributeError: Use 'new_bar' instead

python3.10 foo.py

Traceback (most recent call last):
  File "/tmp/foo.py", line 11, in <module>
    Foo().bar
  File "/tmp/foo.py", line 8, in __getattr__
    raise AttributeError("Use 'new_bar' instead.")
AttributeError: Use 'new_bar' instead.. Did you mean: '_bar'?

My question is should the output of a built-in Exception remain constant across releases? I think the answer should be yes unless explicitly agreed upon and documented in release notes. (It wasn’t for 3.10)

As far as the fix, updating the documentation would be best – changing the behavior from the current 3.13 back to the 3.9 way would be another API change for minimal benefit.

No, exception text is not guaranteed to remain the same across versions.

Agreed.

2 Likes

If None is documented, it should also be tested.

2 Likes

After a few days, I think setting name to None might be a bad idea. Although it seems to be only used for name suggestions at the moment, the name attribute is completely generic, and could be used for many other purposes, either in future versions of Python or for a typical user.

Having a new flag attribute like “name_suggestions” seems like the best solution to me.