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

Nick Coghlan ncoghlan at gmail.com
Mon Mar 16 12:43:11 CET 2009


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
---------------------------------------------------------------


More information about the Python-Dev mailing list