On 19 November 2016 at 07:00, Greg Ewing <greg.ew...@canterbury.ac.nz> wrote:
> Ram Rachum wrote:
>>
>> 1. I'm making a program that lets people lease machines. They can issue a
>> command to lease 7 machines. ... If everything goes fine, I do pop_all on
>> the exit stack so it doesn't get exited and the machines stay leased,
>
>
> Seems to me that would be done more easily and clearly
> without involving a context manager at all

ExitStack() handles that case by design - doing "pop_all() gives you a
fresh ExitStack() instance, while making close() and __exit__() on the
original no-ops. Since ExitStack() deliberately never cleans up the
stack implicitly, you end up being able to do:

    machines = []
    with contextlib.ExitStack() as cleanup:
        for i in range(num_machines_required):
            machines.append(lease_machine())
            cleanup.callback(release_machine, machine)
        cleanup.pop_all() # It worked, so don't clean anything up yet
    return machines

However, ExitStack *itself* can't readily be written using
contextlib.contextmanager - it's not impossible, but it's convoluted
enough not to be worth the hassle, since you'd need to do something
like:

    class _ExitStack:
        # Like the current ExitStack, but without __enter__/__exit__

    @contextmanager
    def exit_stack()
        state = _ExitStack()
        try:
            yield state
        finally:
            state.close()

Externally, the visible differences would be that:

- "es_cm = exit_stack()" and "es_state = exit_stack().__enter__()"
would give different results
- it would be slower and use more memory due to the additional layer
of indirection

Since the extra layer of indirection doesn't buy you any new
capabilities and has a runtime performance cost, there's no real
reason to do it.

The general case of that pattern is to yield a state variable that
just has a single attribute "needs_cleanup":

    @contextmanager
    def my_cm()
        ... # Do setup operations here
        state = types.SimpleNamespace(needs_cleanup=True)
        try:
            yield state
        finally:
            if state.needs_cleanup:
                ... # Do cleanup operations here

However, as with ExitStack, at that point, you're usually going to be
better off just implementing __enter__ and __exit__ yourself, and not
worrying about using the generator format.

This kind of difference in flexibility isn't really specific to
context managers though - it's a particular instance of the general
pattern that custom classes are often more convenient than closures
when you actually *want* to expose externally mutable state.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to