Skip to content

traceback._compute_suggestion_error crashes on __getattr__ raising #129605

Closed

Description

@jcea

Bug report

Bug description:

This is a regression introduced in 3.13 because the new pyrepl. 3.14 is also affected.

Type this in an interactive 3.13/3.14 session:

>>> class a:
...     def __getattr__(self, x):
...         print(x)
...         print(qq)
...         
>>> b=a()
>>> b.pepe
pepe
qq
qq
Traceback (most recent call last):
  File "<python-input-5>", line 1, in <module>
  File "<python-input-3>", line 4, in __getattr__
NameError: name 'qq' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.13/_pyrepl/__main__.py", line 6, in <module>
    __pyrepl_interactive_console()
  File "/usr/local/lib/python3.13/_pyrepl/main.py", line 59, in interactive_console
    run_multiline_interactive_console(console)
  File "/usr/local/lib/python3.13/_pyrepl/simple_interact.py", line 160, in run_multiline_interactive_console
    more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single")  # type: ignore[call-arg]
  File "/usr/local/lib/python3.13/code.py", line 314, in push
    more = self.runsource(source, filename, symbol=_symbol)
  File "/usr/local/lib/python3.13/_pyrepl/console.py", line 211, in runsource
    self.runcode(code)
  File "/usr/local/lib/python3.13/code.py", line 96, in runcode
    self.showtraceback()
  File "/usr/local/lib/python3.13/code.py", line 129, in showtraceback
    self._showtraceback(typ, value, tb.tb_next, '')
  File "/usr/local/lib/python3.13/code.py", line 145, in _showtraceback
    self._excepthook(typ, value, tb)
  File "/usr/local/lib/python3.13/_pyrepl/console.py", line 169, in _excepthook
    lines = traceback.format_exception(
  File "/usr/local/lib/python3.13/traceback.py", line 154, in format_exception
    te = TracebackException(type(value), value, tb, limit=limit, compact=True)
  File "/usr/local/lib/python3.13/traceback.py", line 1090, in __init__
    suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
  File "/usr/local/lib/python3.13/traceback.py", line 1516, in _compute_suggestion_error
    if hasattr(self, wrong_name):
  File "<python-input-3>", line 4, in __getattr__
NameError: name 'qq' is not defined

And the interactive python session is terminated.

Under previous Python releases, with no pyrepl, the right exception is raised and the repl keeps going:

Python 3.9.21 (main, Dec  9 2024, 19:43:35) 
[GCC 10.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class a:
...   def __getattr__(self, x):
...     print(x)
...     print(qq)
... 
>>> b=a()
>>> b.pepe
pepe
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __getattr__
NameError: name 'qq' is not defined
>>> 

Notice that this problem raises SPECIFICALLY if __getattr__ raises a NameError exception. Other exceptions, like division by zero, don't break pyrepl.

CPython versions tested on:

3.13, 3.12, 3.11, 3.10, 3.9, 3.14

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added
    topic-replRelated to the interactive shell
    type-bugAn unexpected behavior, bug, or error
    on Feb 3, 2025
  2. skirpichev commented on Feb 3, 2025

    @skirpichev
    Member

    A quick fix (I'm not sure it's a right way):

    diff --git a/Lib/traceback.py b/Lib/traceback.py
    index 31c73efcef..abcc099c29 100644
    --- a/Lib/traceback.py
    +++ b/Lib/traceback.py
    @@ -1520,8 +1520,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
             # has the wrong name as attribute
             if 'self' in frame.f_locals:
                 self = frame.f_locals['self']
    -            if hasattr(self, wrong_name):
    -                return f"self.{wrong_name}"
    +            try:
    +                if hasattr(self, wrong_name):
    +                    return f"self.{wrong_name}"
    +            except NameError:
    +                pass
     
         try:
             import _suggestions
    >>> b.pepe
    pepe
    qq
    Traceback (most recent call last):
      File "<python-input-2>", line 1, in <module>
        b.pepe
      File "<python-input-0>", line 4, in __getattr__
        print(qq)
              ^^
    NameError: name 'qq' is not defined
  3. jcea commented on Feb 3, 2025

    @jcea
    MemberAuthor

    Cool, but "qq" should not be searched in the class. Notice that __getattr__ is called twice, first for "pepe" and another one, incorrectly, for "qq".

  4. skirpichev commented on Feb 3, 2025

    @skirpichev
    Member

    That might be helpful for suggestions, see 99e2e60.

  5. lopuhin commented on Feb 25, 2025

    @lopuhin

    Another instance of the same error:

    import traceback
    
    
    class C:
        @property
        def foo(self):
            return foo
    
    try:
        C().foo
    except Exception:
        print(traceback.format_exc())
    

    gives (at least on Python 3.12):

    % python t.py
    Traceback (most recent call last):
      File "/Users/konstantin/t.py", line 10, in <module>
        C().foo
      File "/Users/konstantin/t.py", line 7, in foo
        return foo
               ^^^
    NameError: name 'foo' is not defined
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/Users/konstantin/t.py", line 12, in <module>
        print(traceback.format_exc())
              ^^^^^^^^^^^^^^^^^^^^^^
      File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 184, in format_exc
        return "".join(format_exception(sys.exception(), limit=limit, chain=chain))
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 139, in format_exception
        te = TracebackException(type(value), value, tb, limit=limit, compact=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 767, in __init__
        suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 1100, in _compute_suggestion_error
        if hasattr(self, wrong_name):
           ^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/konstantin/t.py", line 7, in foo
        return foo
               ^^^
    NameError: name 'foo' is not defined
    

    so the property gets re-executed. In other cases this can also lead to an infinite chain of exceptions.

  6. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Feb 25, 2025
  7. changed the title [-]Crashing "pyrepl" with "__getattr__"[/-] [+]traceback._compute_suggestion_error crashes on `__getattr__` raising[/+] on Mar 20, 2025
  8. removed
    topic-replRelated to the interactive shell
    on Mar 20, 2025
  9. kak-0 commented on Apr 14, 2025

    @kak-0

    I encountered this bug when trying to log such an exception using logging.

    The following code demonstrates the issue (using ValueError for demonstration purposes, even though the real issue I had was a NameError due to forgetting to write self.).

    import logging
    
    class Foo:
        @property
        def x(self):
            raise ValueError("x")
    
        def y(self):
            return x
    
    logger = logging.getLogger(__name__)
    
    try:
        Foo().y()
    except Exception as e:
        logger.exception("y failed") # raises!
    
    print("done") # not reached!

    logger.exception prints "--- Logging error ---" and then calls traceback.print_exception which fails to print the traceback. The exception raised by hasattr is propagated by logger.exception, preventing the program from continuing!

    I think this should be considered a bug in logging: logger.exception should probably silently drop any exceptions raised while trying to handle logging errors instead of propagating the exceptions.

  10. duaneg commented on Jun 13, 2025

    @duaneg
    Contributor

    This cannot be reproduced in v3.14: bisection confirms it was fixed in 641253c, for #132385. We can probably close this.

  11. skirpichev commented on Jun 13, 2025

    @skirpichev
    Member

    Looks valid in the master:

    Python 3.15.0a0 (heads/main-dirty:2b0c684e07, Jun 13 2025, 06:46:26) [Clang 14.0.6 ] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> class a:
    ...     def __getattr__(self, x):
    ...         print(x)
    ...         print(qq)
    ...         
    ... b=a()
    ... 
    >>> b.pepe
    pepe
    qq
    Traceback (most recent call last):
      File "<python-input-1>", line 1, in <module>
        b.pepe
      File "<python-input-0>", line 4, in __getattr__
        print(qq)
              ^^
    NameError: name 'qq' is not defined
    >>> 
  12. jcea commented on Nov 11, 2025

    @jcea
    MemberAuthor

    Current Python 3.13.8 and 3.14.0 are not crashing anymore. I close this issue.

  13. shenker commented on May 30, 2026

    @shenker

    I found another way to reproduce this bug that still works in 3.14.4. PR #129632's approach of wrapping the NameError branch in try/except Exception: return None would fix this.

    Here's the real-world reproducer (and how I came across this bug). Create a marimo (0.23.8) notebook with the following cell (that imports statsmodel 0.14.6, which in turn imports patsy 1.0.2).

    import polars as pl
    import statsmodels.formula.api as smf
    
    df = pl.DataFrame({"a2": range(10), "b": range(10, 20)})
    model = smf.ols(
        "a ~ b",
        data=df.to_pandas(),
    )

    The CPython process running the marimo kernel will crash when the cell is run. A misspelled column name in a formula string triggers a NameError inside patsy's eval() call (where f_locals is a live VarLookupDict). Marimo calls traceback.print_exception directly from Python to format cell errors — unlike the REPL, which uses C-level PyErr_Display and is robust to this — so the KeyError(0) propagates uncaught and kills the notebook kernel.

    This minimal reproducer mocks patsy's VarLookupDict, which implements __getitem__ but not __iter__, causing Python to fall back to the legacy sequence protocol (__getitem__(0), __getitem__(1), ...) and immediately raise KeyError(0):

    import traceback
    
    class LegacyMapping:
        """Minimal repro of patsy.eval.VarLookupDict — implements __getitem__ but not __iter__."""
        def __getitem__(self, key):
            if key == "x":
                return 1
            raise KeyError(key)  # raises KeyError for integer keys too
    
    try:
        eval("nonexistent_var", {}, LegacyMapping())
    except NameError as e:
        traceback.print_exception(e)
        # Expected: prints NameError cleanly
        # Actual (3.13+): KeyError: 0 propagates out of print_exception
  14. added 3 commits that reference this issue on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions