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

Ram Rachum ram at rachum.com
Sun Nov 6 01:07:27 EST 2016


Heh, I just played with this, and found a workaround. If I do something
like this after creating the generator:

sys.g = g

Then it wouldn't get closed when Python finishes, and the cleanup won't
happen, which is what I want. This is still a bit ugly but now it's
something I can do automatically for every generator-based context manager
that I create, so it's not as bad as the previous workaround in my opinion.
(I wouldn't create a new attribute for each context manager, but just have
a list with a mangled name on `sys` that'll hold all of them.)

On Sun, Nov 6, 2016 at 8:02 AM, Ram Rachum <ram at rachum.com> wrote:

> Sorry, I was wrong at quoting the workaround I do, it's actually this:
>
>             try:
>                 yield
>             except GeneratorExit:
>                 raise
>             except:
>                 cleanup()
>                 raise
>             else:
>                 cleanup()
>
> This works, but it's ugly! And I basically need to do this to every
> generator-based context manager that I want to be able to run just the
> `__enter__` of without the `__exit__`. I wish there was a better solution
> than including this in my code.
>
> On Sun, Nov 6, 2016 at 7:51 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:
>
>> 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
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20161106/ed9431f4/attachment-0001.html>


More information about the Python-ideas mailing list