Skip to content

Generate AttributeError messages from their context #153785

Description

@johnslavik

Feature or enhancement

Proposal:

People very often raise AttributeError(name) (~40 occurrences in CPython repo) or raise AttributeError (bare, this is rarer), but when displaying the traceback this misses either the object targeted or both the object and the attribute name.

That information is already there. AttributeError.obj and AttributeError.name is automatically set via _PyObject_SetAttributeErrorContext and now is only used to calculate attribute suggestions. Let's improve error messages with it too.

Consider a few examples where the user could raise a bare AttributeError or AttributeError("attr"), how we handle it (-), and how we could handle it (+):

import traceback

class Foo1:
    def __getattr__(self, name):
        raise AttributeError(name)

try:
    Foo1().missing1
except AttributeError as a:
    print(f"-{traceback.format_exc().splitlines()[-1]}")
    print(f"+AttributeError: {a.obj} has no attribute {a.name!r}\n")

class Foo2:
    def __getattr__(self, name):
        raise AttributeError

try:
    Foo2().missing2
except AttributeError as a:
    print(f"-{traceback.format_exc().splitlines()[-1]}")
    print(f"+AttributeError: {a.obj} has no attribute {a.name!r}\n")

import mod1  # def __getattr__(name): raise AttributeError(name)
try:
    mod1.missing3
except AttributeError as a:
    print(f"-{traceback.format_exc().splitlines()[-1]}")
    print(f"+AttributeError: {a.obj} has no attribute {a.name!r}\n")

import mod2  # def __getattr__(name): raise AttributeError
try:
    mod2.missing4
except AttributeError as a:
    print(f"-{traceback.format_exc().splitlines()[-1]}")
    print(f"+AttributeError: {a.obj} has no attribute {a.name!r}\n")
-AttributeError: missing1
+AttributeError: <__main__.Foo1 object at 0x109f2c8a0> has no attribute 'missing1'

-AttributeError
+AttributeError: <__main__.Foo2 object at 0x109f2c8a0> has no attribute 'missing2'

-AttributeError: missing3
+AttributeError: <module 'mod1' from './Python/cpython/mod1.py'> has no attribute 'missing3'

-AttributeError
+AttributeError: <module 'mod2' from './Python/cpython/mod2.py'> has no attribute 'missing4'

If we generate messages from collected context, we'll also fix some edge cases in the stdlib and snake knows where else. Consider this functools.singledispatchmethod edge case:

from functools import singledispatchmethod
 
class C:
    @singledispatchmethod
    def sdm(self, a):
        pass
        
C().sdm.__code__

which currently fails with - but could be +:

-AttributeError: . Did you mean: '__call__'?
+AttributeError: '_singledispatchmethod_get' object has no attribute '__code__'. Did you mean: '__call__'?

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

No response

Linked PRs

Metadata

Metadata

Assignees

Labels

interpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions