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.

classification
Title: NameError for built in function open when re-raising stored exception from yielded function
Type: behavior Stage:
Components: Interpreter Core Versions: Python 3.11, Python 3.10, Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: davide.rizzo, iritkatriel, martin.panter, pitrou, reidfaiv, rooter, serhiy.storchaka, vstinner
Priority: normal Keywords:

Created on 2016-03-10 12:09 by reidfaiv, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
gen_err.py reidfaiv, 2016-03-10 12:09
Messages (10)
msg261497 - (view) Author: (reidfaiv) Date: 2016-03-10 12:09
Builtin open() gets NameError) in context manager __exit__ in case:
* context manager yielding records
* we return another generator when consuming these and
* second generator raises, catches, stores and re-raises an excption


Reduced down code looks like this:
---------------------------------------
#!/usr/bin/env python
# -*- coding: utf-8 -*-


# context manager "query" (open in init, yield rows and close on exit)
class _query():
    def __init__(self, **kwargs):
        pass

    def __enter__(self):
        return [1, 2, 3]

    def __exit__(self, type_, value, tb):
        print('__exit__')           # print() is also built-in, but works
        f = open('test.log', 'wt')  # <-- NameError: name 'open' is not defined
        f.close()


# this works fine
def a():
    try:
        raise Exception('volatile exception')
    except Exception as e:
        raise e


# this does not work
def b():
    try:
        raise Exception('stored exception')
    except Exception as e:
        ee = e  # <-- storing exception and then
    raise ee    # <-- re-raising stored exception triggers the issue


def event_gen(**kwargs):
    with _query() as cursor:
        for _ in cursor:
            yield b     # <--- does not work
            # yield a   # <--- works fine


def run(**kwargs):
    g = event_gen(**kwargs)
    r = next(g)
    r()
    # This also works
    # for r in event_gen(**kwargs):
    #     r()

if __name__ == '__main__':
    run()


---------------------------------------
>python.exe gen_err.py
Traceback (most recent call last):
  File "gen_err.py", line 52, in <module>
    run()
  File "gen_err.py", line 46, in run
    r()
  File "gen_err.py", line 33, in b
    raise ee    # <-- re-raising stored exception triggers the issue
  File "gen_err.py", line 30, in b
    raise Exception('stored exception')
Exception: stored exception
__exit__
Exception ignored in: <generator object event_gen at 0x000001A31F7C4048>
Traceback (most recent call last):
  File "gen_err.py", line 39, in event_gen
  File "gen_err.py", line 15, in __exit__
NameError: name 'open' is not defined


This happens with Python 3.4+
Works with earlier versions as expected.

If exception re-raised immediately, works fine.
If outermost generator is consumed in for loop, works fine.
msg261499 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2016-03-10 13:15
What is probably happening here is how garbage collection works at the interpreter exit. It is sort of mentioned in the warning at <https://docs.python.org/3/reference/datamodel.html#object.__del__>.

The variable g is an iterator for event_gen(), and it is still “alive” when the exception is raised. The exception contains a reference to the traceback and all the local variables, including g. So the context manager in the generator cannot exit until g is deleted [or g.close() is called].

In the “volatile exception” case, I guess it is easy for the interpreter to release the exception traceback, including the g iterator that it references, straight after it has reported the traceback.

But when you store an exception that you catch in a local variable, you create a reference loop (something like ee.__traceback__.tb_frame.f_locals["ee"] is ee). So it is not so easy to release the references, and by the time the garbage collector runs, I guess it has already set builtin names like “open” (global variable of the “builtins” module) to None.

If you need a generator to clean up, you should probably close the generator before exiting the function rather than relying on garbage collection. Unfortunately there is no mechanism like ResourceWarning to indicate this problem with generators.
msg261500 - (view) Author: (reidfaiv) Date: 2016-03-10 14:17
Indeed, explicitly closing generator helps, following works:

def run(**kwargs):
    g = event_gen(**kwargs)
    r = next(g)
    g.close()
    r()

Thanks!

Do you want to keep this issue open for investigation?
msg261524 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2016-03-10 21:39
Actually I was a bit hasty in my response last night. NameError indicates that the “open” symbol has been deleted, not just set to None. If you add print(sorted(builtins.__dict__.keys())) inside __exit__() you can verify this is the case.

This contradicts my understanding of the finalization process (symbols being deleted rather than being set to None). Maybe the documentation needs fixing?
msg261550 - (view) Author: (reidfaiv) Date: 2016-03-11 08:44
def __exit__(self, type_, value, tb):
        print('__exit__')           # print() is also built-in, but works
        print(sorted(builtins.__dict__.keys()))
        f = open('test.log', 'wt')  # <-- NameError: name 'open' is not defined
        f.close()

I will get following. Clearly "open" is missing:

Traceback (most recent call last):
  File "gen_err.py", line 56, in <module>
    run()
  File "gen_err.py", line 50, in run
    r()
  File "gen_err.py", line 36, in b
    raise ee    # <-- re-raising stored exception triggers the issue
  File "gen_err.py", line 33, in b
    raise Exception('stored exception')
Exception: stored exception
__exit__
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'ord', 'pow', 'print', 'property', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
Exception ignored in: <generator object event_gen at 0x0000027F7C794048>
Traceback (most recent call last):
  File "gen_err.py", line 42, in event_gen
  File "gen_err.py", line 18, in __exit__
NameError: name 'open' is not defined
msg297950 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-07-08 08:00
The interpreter makes a copy of the builtins module dict at startup and restores it at shutdown. open() is imported from the io module after making the copy, and therefore it is absent in restored module.
msg309462 - (view) Author: Maciej Urbański (rooter) * Date: 2018-01-04 09:50
Reproduced in both v3.6.4 and v3.7.0a3
msg340783 - (view) Author: Davide Rizzo (davide.rizzo) * Date: 2019-04-24 15:22
I've just stumbled on the same thing happening on some code that attempts to use logging on __del__.

Comparing dir(__builtins__) normally and on shutdown, these are missing:

['copyright', 'credits', 'exit', 'help', 'license', 'open', 'quit']

The traceback of the real error looks like this:

  [...]
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 1383, in info
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 1519, in _log
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 1529, in handle
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 1591, in callHandlers
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 905, in handle
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 1131, in emit
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 1121, in _open
NameError: name 'open' is not defined
msg380026 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2020-10-31 00:41
Davide Rizzo:
> I've just stumbled on the same thing happening on some code that attempts to use logging on __del__.
> (...)
> File ".../logging/__init__.py", line 1121, in _open
> NameError: name 'open' is not defined

This is bpo-26789 which is unrelated to this issue.
msg408504 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2021-12-14 00:10
Reproduced on 3.11.
History
Date User Action Args
2022-04-11 14:58:28adminsetgithub: 70715
2021-12-14 00:10:12iritkatrielsetnosy: + iritkatriel

messages: + msg408504
versions: + Python 3.9, Python 3.10, Python 3.11, - Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8
2020-10-31 00:41:29vstinnersetmessages: + msg380026
2019-04-24 15:22:24davide.rizzosetnosy: + davide.rizzo

messages: + msg340783
versions: + Python 3.8
2018-01-04 09:50:03rootersetnosy: + rooter

messages: + msg309462
versions: + Python 3.6, Python 3.7
2017-07-08 08:00:38serhiy.storchakasetnosy: + vstinner, serhiy.storchaka, pitrou
messages: + msg297950
2016-03-11 08:44:46reidfaivsetmessages: + msg261550
2016-03-10 21:39:06martin.pantersetmessages: + msg261524
2016-03-10 14:17:02reidfaivsetmessages: + msg261500
2016-03-10 13:15:44martin.pantersetnosy: + martin.panter
messages: + msg261499
2016-03-10 12:09:57reidfaivcreate