Strange side effect of the generator that causes finalization

There is a C module’s crash report on the bug tracker. When reproducing the issue on 3.14 alpha7, I saw even CPython 3.3 and newer behave almost the same way. The following scripts and results were checked with non-interactive mode on Windows. In the unexpected-marked cases, the finally block seems to invoke the GC collection. Is that by design, or fixable?

Expected:

def gen():
    try:
        yield
    finally:
        print(sys.modules)

next(gen())


# Output:

{'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>,
 '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' 
 ...

Unexpected to me:

def gen():
    try:
        yield
    finally:
        print(sys.modules)

it = gen()  # place outside the next()
next(it)


# Output (empty dict):

{}
def gen():
    try:
        yield
    finally:
        import itertools

it = gen()
next(it)


# Output:

ImportError: sys.meta_path is None, Python is likely shutting down

I don’t know what you mean by “contained”, but what you’re seeing here is a quirk of interpreter shutdown. You can see it in a variety of ways, but they all come down to having code that runs while everything’s being unwound for termination. That’s why it behaves differently depending on whether you capture the generator in a variable or not; in your first example, it gets garbage collected early, but your second one retains it until the shutdown is happening.

Can you elaborate on the “C module’s crash report” that you referred to? Possibly link to the tracker issue in question?

If you can get a C-level crash (such as a segfault), that should probably be fixed, although it may not be easy. But if it’s the exception that you’re showing here, that’s almost certainly not going to change; by the time you get to that shutdown, the import machinery isn’t available any more. The best solution here is to ensure that you import whatever you need BEFORE any such last-ditch reporting - even if that means importing something at top level that you otherwise wouldn’t need until exception handling.

Thank you for the suggestion.

Issue: SIGSEV in `datetime.timedelta` (possibly from datetime's C `delta_new`) · Issue #132413 · python/cpython · GitHub.

UPDATE: Reworded the title.