The discussion on the meaning of break when nesting a PEP 340 block statement inside a for loop has given me some real reasons to prefer PEP 310's single pass semantics for user defined statements (more on that at the end). The suggestion below is my latest attempt at combining the ideas of the two PEP's.
For the keyword, I've used the abbreviation 'stmt' (for statement). I find it reads pretty well, and the fact that it *isn't* a real word makes it easier for me to track to the next item on the line to find out the actual statement name (I think this might be similar to the effect of 'def' not being a complete word making it easier for me to pick out the function name). I consequently use 'user statement' or 'user defined statement' to describe what PEP 340 calls anonymous block statements. I'm still fine with the concept of not using a keyword at all, though. Cheers, Nick. == User Defined Statement Usage Syntax == stmt EXPR1 [as VAR1]: BLOCK1 == User Defined Statement Semantics == the_stmt = EXPR1 terminated = False try: stmt_enter = the_stmt.__enter__ stmt_exit = the_stmt.__exit__ except AttributeError: raise TypeError("User statement required") try: VAR1 = stmt_enter() # Omit 'VAR1 =' if no 'as' clause except TerminateBlock: pass # Block is not entered at all in this case # If an else clause were to be permitted, the # associated block would be executed here else: try: try: BLOCK1 except: exc = sys.exc_info() terminated = True try: stmt_exit(*exc) except TerminateBlock: pass finally: if not terminated: try: stmt_exit(TerminateBlock) except TerminateBlock: pass Key points: * The supplied expression must have both __enter__ and __exit__ methods. * The result of the __enter__ method is assigned to VAR1 if VAR1 is given. * BLOCK1 is not executed if __enter__ raises an exception * A new exception, TerminateBlock, is used to signal statement completion * The __exit__ method is called with the exception tuple if an exception occurs * Otherwise it is called with TerminateBlock as the argument * The __exit__ method can suppress an exception by converting it to TerminateBlock or by returning without reraising the exception * return, break, continue and raise StopIteration are all OK inside BLOCK1. They affect the surrounding scope, and are in no way tampered with by the user defined statement machinery (some user defined statements may choose to suppress the raising of StopIteration, but the basic machinery doesn't do that) * Decouples user defined statements from yield expressions, the enhanced continue statement and generator finalisation. == New Builtin: statement == def statement(factory): try: factory.__enter__ factory.__exit__ # Supplied factory is already a user statement factory return factory except AttributeError: # Assume supplied factory is an iterable factory # Use it to create a user statement factory class stmt_factory(object): def __init__(*args, **kwds) self = args[0] self.itr = iter(factory(*args[1:], **kwds)) def __enter__(self): try: return self.itr.next() except StopIteration: raise TerminateBlock def __exit__(self, *exc_info): try: stmt_exit = self.itr.__exit__ except AttributeError: try: self.itr.next() except StopIteration: pass raise *exc_info # i.e. re-raise the supplied exception else: try: stmt_exit(*exc_info) except StopIteration: raise TerminateBlock Key points: * The supplied factory is returned unchanged if it supports the statement API (such as a class with both __enter__ and __exit__ methods) * An iterable factory (such as a generator, or class with an __iter__ method) is converted to a block statement factory * Either way, the result is a callable whose results can be used as EXPR1 in a user defined statement. * For statements constructed from iterators, the iterator's next() method is called once when entering the statement, and the result is assigned to VAR1 * If the iterator has an __exit__ method, it is invoked when the statement is exited. The __exit__ method is passed the exception information (which may indicate that no exception occurred). * If the iterator does not have an __exit__ method, it's next() method is invoked a second time instead * When an iterator is used to drive a user defined statement, StopIteration is translated to TerminateBlock * Main intended use is as a generator decorator * Decouples user defined statements from yield expressions, the enhanced continue statement and generator finalisation. == Justification for non-looping semantics == For most use cases, the effect PEP 340 block statements have on break and continue statements is both surprising and undesirable. This is highlighted by the major semantic difference between the following two cases: stmt locking(lock): for item in items: if handle(item): break for item in items: stmt locking(lock): if handle(item): break Instead of simply acquiring and releasing the lock on each iteration, as one would legitimately expect, the latter piece of code actually processes all of the items, instead of breaking out of the loop once one of the items is handled. With non-looping user defined statements, the above code works in the obvious fashion (the break statement ends the for loop, not the lock acquisition). With non-looping semantics, the implementation of the examples in PEP 340 is essentially identical - just add an invocation of @statement to the start of the generators. It also becomes significantly easier to write user defined statements manually as there is no need to track state: class locking: def __init__(self, lock): self.lock = lock def __enter__(self): self.lock.acquire() def __exit__(self, exc_type, value=None, traceback=None): self.lock.release() if type is not None: raise exc_type, value, traceback The one identified use case for a user-defined loop was PJE's auto_retry. We already have user-defined loops in the form of custom iterators, and there is nothing stopping an iterator from returning user defined statements like this: for attempt in auto_retry(3, IOError): stmt attempt: # Do something! # Including break to give up early # Or continue to try again without raising IOError The implementation of auto-retry is messier than it is with all user defined statement being loops, but I think the benefits of non-looping semantics justify that sacrifice. Besides, it really isn't all that bad: class auto_retry(3, IOError): def __init__(self, times, exc=Exception): self.times = xrange(times-1) self.exc = exc self.succeeded = False def __iter__(self): attempt = self.attempt for i in self.times: yield attempt() if self.succeeded: break else: yield self.last_attempt() @statement def attempt(self): try: yield None self.succeeded = True except self.exc: pass @statement def last_attempt(self): yield None (Third time lucky! One day I'll remember that Python has these things called classes designed to elegantly share state between a collection of related functions and generators. . .) The above code for auto_retry assumes that generators supply an __exit__ method as described in PEP 340 - without that, auto_retry.attempt would need to be written as a class since it needs to know if an exception was thrown or not: class auto_retry(3, IOError): def __init__(self, times, exc=Exception): self.times = xrange(times-1) self.exc = exc self.succeeded = False def __iter__(self): attempt = self.attempt for i in self.times: yield attempt(self) if self.succeeded: break else: yield self.last_attempt() class attempt(object): def __init__(self, outer): self.outer = outer def __enter__(self): pass def __exit__(self, exc_type, value=None, traceback=None): if exc_type is None: self.outer.succeeded = true elif exc_type not in self.outer.exc raise exc_type, value, traceback @statement def last_attempt(self): yield None -- Nick Coghlan | [EMAIL PROTECTED] | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.skystorm.net _______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com