[Python-Dev] Re: anonymous blocks

Brian Sabbey sabbey at u.washington.edu
Wed Apr 27 02:45:01 CEST 2005


Nick Coghlan wrote:
> Accordingly, I would like to suggest that 'with' revert to something 
> resembling the PEP 310 definition:
>
>    resource = EXPR
>    if hasattr(resource, "__enter__"):
>        VAR = resource.__enter__()
>    else:
>        VAR = None
>    try:
>        try:
>            BODY
>        except:
>            raise # Force realisation of sys.exc_info() for use in __exit__()
>    finally:
>        if hasattr(resource, "__exit__"):
>            VAR = resource.__exit__()
>        else:
>            VAR = None
>
> Generator objects could implement this protocol, with the following 
> behaviour:
>
>    def __enter__():
>        try:
>            return self.next()
>        except StopIteration:
>            raise RuntimeError("Generator exhausted, unable to enter with 
> block")
>
>    def __exit__():
>        try:
>            return self.next()
>        except StopIteration:
>            return None
>
>    def __except__(*exc_info):
>        pass
>
>    def __no_except__():
>        pass

One peculiarity of this is that every other 'yield' would not be allowed 
in the 'try' block of a try/finally statement (TBOATFS).  Specifically, a 
'yield' reached through the call to __exit__ would not be allowed in the 
TBOATFS.

It gets even more complicated when one considers that 'next' may be called 
inside BODY.  In such a case, it would not be sufficient to just disallow 
every other 'yield' in the TBOATFS.  It seems like 'next' would need some 
hidden parameter that indicates whether 'yield' should be allowed in the 
TBOATFS.

(I assume that if a TBOATFS contains an invalid 'yield', then an exception 
will be raised immediately before its 'try' block is executed.  Or would 
the exception be raised upon reaching the 'yield'?)


> These are also possible by combining a normal for loop with a non-looping
> with (but otherwise using Guido's exception injection semantics):
>
> def auto_retry(attempts):
>    success = [False]
>    failures = [0]
>    except = [None]
>
>    def block():
>        try:
>            yield None
>        except:
>            failures[0] += 1
>        else:
>            success[0] = True
>
>    while not success[0] and failures[0] < attempts:
>        yield block()
>    if not success[0]:
>        raise Exception # You'd actually propagate the last inner failure
>
> for attempt in auto_retry(3):
>    with attempt:
>        do_something_that_might_fail()

I think your example above is a good reason to *allow* 'with' to loop. 
Writing 'auto_retry' with a looping 'with' would be pretty straightforward 
and intuitive.  But the above, non-looping 'with' example requires two 
fairly advanced techniques (inner functions, variables-as-arrays trick) 
that would probably be lost on some python users (and make life more 
difficult for the rest).

But I do see the appeal to having a non-looping 'with'.  In many (most?) 
uses of generators, 'for' and looping 'with' could be used 
interchangeably.  This seems ugly-- more than one way to do it and all 
that.

-Brian


More information about the Python-Dev mailing list