[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