[Python-Dev] PEP 377 - allow __enter__() methods to skip the statement body

Guido van Rossum guido at python.org
Mon Mar 16 19:06:01 CET 2009

Moreover, since the main use case seems to be fixing a corner case of
the nested() context manager, perhaps the effort towards changing the
language would be better directed towards supporting "with a, b:" as a
shorthand for "with a: with b:" .

On Mon, Mar 16, 2009 at 10:01 AM, Guido van Rossum <guido at python.org> wrote:
> I have no right to speak because I haven't read through all the
> details of the proposal, but reading this I am very sad that we have
> to introduce a whole new exception (and one with special status as
> well) in order to fix such a niggly corner case of the context manager
> protocol.
> Since IIUC the original context manager design was intended to have
> exactly one yield in the body of the context manager -- can't we just
> declare fewer (or more) yields an error and rase an appropriate
> TypeError or something?
> --Guido
> On Mon, Mar 16, 2009 at 4:43 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:
>> Michael Foord wrote:
>>> Well, StopIteration is still an implementation detail that only
>>> occasionally bleeds through to actual programming. It says nothing about
>>> whether using exceptions for non-exceptional circumstances (control
>>> flow) is good practise. Personally I think it makes the intent of code
>>> less easy to understand - in effect the exceptions *are* being used as a
>>> goto.
>> Note that raising SkipStatement manually is likely to be even rarer than
>> raising StopIteration. Catching it should almost never happen other than
>> implicitly inside a with statement (that's the reason I made it a peer
>> of SystemExit and GeneratorExit rather than a peer of StopIteration).
>> It is primarily proposed as a way for contextlib.contextmanager to tell
>> the interpreter that the underlying generator didn't yield, so the body
>> of the with statement should be skipped completely. It just so happens
>> that manually implemented context managers will also be free to use it
>> if they need to for some reason.
>> An alternative approach worth considering may be to use NotImplemented
>> as a model instead of StopIteration. With that approach, instead of
>> having SkipStatement be an exception, have it be a singleton that can be
>> returned from __enter__ to indicate that the with statement body would
>> be skipped.
>> That has a big advantage over using an exception when it comes to
>> execution speed. The statement semantics in that case would become:
>>    mgr = (EXPR)
>>    exit = mgr.__exit__  # Not calling it yet
>>    value = mgr.__enter__()
>>    if value is not SkipStatement:
>>        exc = True
>>        try:
>>            try:
>>                VAR = value  # Only if "as VAR" is present
>>                BLOCK
>>            except:
>>                # The exceptional case is handled here
>>                exc = False
>>                if not exit(*sys.exc_info()):
>>                    raise
>>                # The exception is swallowed if exit() returns true
>>        finally:
>>            # The normal and non-local-goto cases are handled here
>>            if exc:
>>                exit(None, None, None)
>> (keeping in mind that I already plan to change PEP 377 to drop the idea
>> of assigning anything to VAR when the statement body is skipped)
>> The major drawback of that approach is that it becomes a little trickier
>> to write a context manager like nested() correctly - it would need to
>> check all of the __enter__() return values and start unwinding the
>> context manager stack if it encountered SkipStatement. The fix isn't
>> particularly complicated*, but it does contrast with the fact that
>> having SkipStatement as an exception means that the current
>> implementation of nested() will "just work" with the new semantics.
>> Cheers,
>> Nick.
>> * For reference, to support a "SkipStatement as return value" approach
>> the main loop in nested() would have to change from this:
>>    for mgr in managers:
>>        exit = mgr.__exit__
>>        enter = mgr.__enter__
>>        vars.append(enter())
>>        exits.append(exit)
>>    yield vars
>> To this:
>>    for mgr in managers:
>>        exit = mgr.__exit__
>>        enter = mgr.__enter__
>>        var = enter()
>>        if var is SkipStatement:
>>            break
>>        vars.append(var)
>>        exits.append(exit)
>>    else:
>>        yield vars
>> As mentioned above, if SkipStatement is an exception then nested() works
>> correctly without any changes.
>> --
>> Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
>> ---------------------------------------------------------------
>> _______________________________________________
>> Python-Dev mailing list
>> Python-Dev at python.org
>> http://mail.python.org/mailman/listinfo/python-dev
>> Unsubscribe: http://mail.python.org/mailman/options/python-dev/guido%40python.org
> --
> --Guido van Rossum (home page: http://www.python.org/~guido/)

--Guido van Rossum (home page: http://www.python.org/~guido/)

More information about the Python-Dev mailing list