[Python-Dev] Re: anonymous blocks

Nick Coghlan 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)
>         try:
>             yield f
>         finally:
>             f.close()
> 
> than having to write a class with __entry__ and __exit__ and
> __except__ methods (I've lost track of the exact proposal at this
> point).

Indeed - the transaction example is very easy to write this way:

     def transaction():
         begin_transaction()
         try:
             yield None
         except:
             abort_transaction()
             raise
         else:
             commit_transaction()

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

     try:
         yield x
     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
      while True:
          try:
              VAR = it.next(err)
          except StopIteration:
              break
          try:
              err = None
              BODY
          except Exception, err: # Pretend "except Exception:" == "except:"
              pass

The generator objects created by using yield would supply the new API, so would 
be usable immediately inside such 'with' blocks.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net


More information about the Python-Dev mailing list