[Python-Dev] Re: anonymous blocks
ncoghlan at gmail.com
Mon Apr 25 11:26:26 CEST 2005
Guido van Rossum wrote:
> It seems that the same argument that explains why generators are so
> good for defining iterators, also applies to the PEP 310 use case:
> it's just much more natural to write
> def with_file(filename):
> f = open(filename)
> yield f
> than having to write a class with __entry__ and __exit__ and
> __except__ methods (I've lost track of the exact proposal at this
Indeed - the transaction example is very easy to write this way:
> Also note that, unlike the for-loop translation, this does *not*
> invoke iter() on the result of EXPR; that's debatable but given that
> the most common use case should not be an alternate looping syntax
> (even though it *is* technically a loop) but a more general "macro
> statement expansion", I think we can expect EXPR to produce a value
> that is already an iterator (rather than merely an interable).
Not supporting iterables makes it harder to write a class which is inherently
usable in a with block, though. The natural way to make iterable classes is to
use 'yield' in the definition of __iter__ - if iter() is not called, then that
trick can't be used.
> Finally, I think it would be cool if the generator could trap
> occurrences of break, continue and return occurring in BODY. We could
> introduce a new class of exceptions for these, named ControlFlow, and
> (only in the body of a with statement), break would raise BreakFlow,
> continue would raise ContinueFlow, and return EXPR would raise
> ReturnFlow(EXPR) (EXPR defaulting to None of course).
Perhaps 'continue' could be used to pass a value into the iterator, rather than
'return'? (I believe this has been suggested previously in the context of for loops)
This would permit 'return' to continue to mean breaking out of the containing
function (as for other loops).
> So a block could return a value to the generator using a return
> statement; the generator can catch this by catching ReturnFlow.
> (Syntactic sugar could be "VAR = yield ..." like in Ruby.)
So, "VAR = yield x" would expand to something like:
except ReturnFlow, ex:
VAR = ReturnFlow.value
> With a little extra magic we could also get the behavior that if the
> generator doesn't handle ControlFlow exceptions but re-raises them,
> they would affect the code containing the with statement; this means
> that the generator can decide whether return, break and continue are
> handled locally or passed through to the containing block.
That seems a little bit _too_ magical - it would be nice if break and continue
were defined to be local, and return to be non-local, as for the existing loop
constructs. For other non-local control flow, application specific exceptions
will still be available.
Regardless, the ControlFlow exceptions do seem like a very practical way of
handling the underlying implementation.
> Note that EXPR doesn't have to return a generator; it could be any
> object that implements next() and next_ex(). (We could also require
> next_ex() or even next() with an argument; perhaps this is better.)
With this restriction (i.e. requiring next_ex, next_exc, or Terry's suggested
__next__), then the backward's compatible version would be simply your desired
semantics, plus an attribute check to exclude old-style iterators:
it = EXPR
if not hasattr(it, "__next__"):
raise TypeError("'with' block requires 2nd gen iterator API support")
err = None
VAR = it.next(err)
err = None
except Exception, err: # Pretend "except Exception:" == "except:"
The generator objects created by using yield would supply the new API, so would
be usable immediately inside such 'with' blocks.
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
More information about the Python-Dev