[Python-ideas] Generator-based context managers can't skip __exit__

Nick Coghlan ncoghlan at gmail.com
Sun Nov 6 01:51:05 EDT 2016


On 6 November 2016 at 14:46, Ram Rachum <ram at rachum.com> wrote:
> I worked around this problem by adding `except GeneratorExit: raise` in my
> context manager, but that's an ugly solution.

Adding `except GeneratorExit: raise` to a try statement with a finally
clause won't prevent the finally clause from executing.

Selective non-idempotent cleanup behaviour really isn't a good idea,
so the language is fighting you for a reason here - the meaning of a
"finally" clause is that the code it contains *will* get executed, and
you have to try incredibly hard to keep that from happening since it's
an implicit part of cleaning up unfinished generators, and we don't
let you switch the nominal class of generator objects at runtime.
Indeed, yield inside try/finally was prohibited for years prior to PEP
342, as the runtime previously couldn't guarantee that the finally
clause would actually execute.

If you *don't* want the code to execute unconditionally, then you need
to be more explicit about when you *do* want it to execute with some
combination of "except" and "else" clauses. For example:

    >>> @contextmanager
    ... def cm():
    ...     print("enter")
    ...     try:
    ...         yield
    ...     except Exception:
    ...         print("Normal exception, not GeneratorExit,
KeyboardInterrupt or SystemExit")
    ...     else:
    ...         print("No exception")
    ...
    >>> cm().__enter__()
    enter

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list