Integrating PEP 310 with PEP 340
This is my attempt at a coherent combination of what I like about both proposals (as opposed to my assortment of half-baked attempts scattered through the existing discussion). PEP 340 has many ideas I like: - enhanced yield statements and yield expressions - enhanced continue and break - generator finalisation - 'next' builtin and associated __next__() slot - changes to 'for' loop One restriction I don't like is the limitation to ContinueIteration and StopIteration as arguments to next(). The proposed semantics and conventions for ContinueIteration and StopIteration are fine, but I would like to be able to pass _any_ exception in to the generator, allowing the generator to decide if a given exception justifies halting the iteration. The _major_ part I don't like is that the block statement's semantics are too similar to those of a 'for' loop. I would like to see a new construct that can do things a for loop can't do, and which can be used in _conjunction_ with a for loop, to provide greater power than either construct on their own. PEP 310 forms the basis for a block construct that I _do_ like. The question then becomes whether or not generators can be used to write useful PEP 310 style block managers (I think they can, in a style very similar to that of the looping block construct from PEP 340). Block statement syntax from PEP 340: block EXPR1 [as VAR1]: BLOCK1 Proposed semantics (based on PEP 310, with some ideas stolen from PEP 340): blk_mgr = EXPR1 VAR1 = blk_mgr.__enter__() try: try: BLOCK1 except Exception, exc: blk_mgr.__except__(exc) else: blk_mgr.__else__() finally: blk_mgr.__exit__() 'blk_mgr' is a hidden variable (as per PEP 340). Note that nothing special happens to 'break', 'return' or 'continue' statements with this proposal. Generator methods to support the block manager protocol used by the block statement: def __enter__(self): try: return next(self) except StopIteration: raise RuntimeError("Generator exhausted before block statement") def __except__(self, exc): try: next(self, exc) except StopIteration: pass def __no_except__(self): try: next(self) except StopIteration: pass def __exit__(self): pass Writing simple block managers with this proposal (these should be identical to the equivalent PEP 340 block managers): def opening(name): opened = open(name) try: yield opened finally: opened.close() def logging(logger, name): logger.enter_scope(name) try: try: yield except Exception, exc: logger.log_exception(exc) finally: logger.exit_scope() def transacting(ts): ts.begin() try: yield except: ts.abort() else: ts.commit() Using simple block managers with this proposal (again, identical to PEP 340): block opening(name) as f: pass block logging(logger, name): pass block transacting(ts): pass Obviously, the more interesting block managers are those like auto_retry (which is a loop, and hence an excellent match for PEP 340), and using a single generator in multiple block statements (which PEP 340 doesn't allow at all). I'll try to get to those tomorrow (and if I can't find any good use cases for the latter trick, then this idea can be summarily discarded in favour of PEP 340). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.skystorm.net
[Nick Coghlan]
This is my attempt at a coherent combination of what I like about both proposals (as opposed to my assortment of half-baked attempts scattered through the existing discussion).
PEP 340 has many ideas I like: - enhanced yield statements and yield expressions - enhanced continue and break - generator finalisation - 'next' builtin and associated __next__() slot - changes to 'for' loop
One restriction I don't like is the limitation to ContinueIteration and StopIteration as arguments to next(). The proposed semantics and conventions for ContinueIteration and StopIteration are fine, but I would like to be able to pass _any_ exception in to the generator, allowing the generator to decide if a given exception justifies halting the iteration.
I'm close to dropping this if we can agree on the API for passing exceptions into __next__(); see the section "Alternative __next__() and Generator Exception Handling" that I just added to the PEP.
The _major_ part I don't like is that the block statement's semantics are too similar to those of a 'for' loop. I would like to see a new construct that can do things a for loop can't do, and which can be used in _conjunction_ with a for loop, to provide greater power than either construct on their own.
While both 'block' and 'for' are looping constructs, their handling of the iterator upon premature exit is entirely different, and it's hard to reconcile these two before Python 3000.
PEP 310 forms the basis for a block construct that I _do_ like. The question then becomes whether or not generators can be used to write useful PEP 310 style block managers (I think they can, in a style very similar to that of the looping block construct from PEP 340).
I've read through your example, and I'm not clear why you think this is better. It's a much more complex API with less power. What's your use case? Why should 'block' be disallowed from looping? TOOWTDI or do you have something better? -- --Guido van Rossum (home page: http://www.python.org/~guido/)
Guido van Rossum wrote:
PEP 310 forms the basis for a block construct that I _do_ like. The question then becomes whether or not generators can be used to write useful PEP 310 style block managers (I think they can, in a style very similar to that of the looping block construct from PEP 340).
I've read through your example, and I'm not clear why you think this is better. It's a much more complex API with less power. What's your use case? Why should 'block' be disallowed from looping? TOOWTDI or do you have something better?
I'm no longer clear on why I thought what I suggested would be better either. Can I use the 'it was late' excuse? :) Actually, the real reason is that I hadn't figured out what was really possible with PEP 340. The cases that I thought PEP 310 would handle better, I've since worked out how to do using the PEP 340 mechanism, and PEP 340 handles them _far_ more elegantly. With PEP 340, multi-stage constructs can be handled by using one generator as an argument to the block, and something else (such as a class or another generator) to maintain state between the blocks. The looping nature is a big win, because it lets execution of a contained block be prevented entirely. My favourite discovery is that PEP 340 can be used to write a switch statement like this: block switch(value) as sw: block sw.case(1): # Handle case 1 block sw.case(2): # Handle case 2 block sw.default(): # Handle default case Given the following definitions: class _switch(object): def __init__(self, switch_var): self.switch_var = switch_var self.run_default = True def case(self, case_value): self.run_default = False if self.switch_var == case_value: yield def default(self): if self.run_default: yield def switch(switch_var): yield _switch(switch_var) With the keyword-less syntax previously mentioned, such a 'custom structure' could look like: switch(value) as sw: sw.case(1): # Handle case 1 sw.case(2): # Handle case 2 sw.default(): # Handle default case (Actually doing a switch using blocks like this would be *insane* for performance reasons, but it is still rather cool that it is possible) With an appropriate utility block manager PEP 340 can also be used to abstract multi-stage operations. I haven't got a real use case for this as yet, but the potential is definitely there: def next_stage(itr): """Execute a single stage of a multi-stage block manager""" arg = None next_item = next(itr) while True: if next_item is StopIteration: raise StopIteration try: arg = yield next_item except: if not hasattr(itr, "__error__"): raise next_item = itr.__error__(sys.exc_info()[1]) else: next_item = next(itr, arg) def multi_stage(): """Code template accepting multiple suites""" # Pre stage 1 result_1 = yield # Post stage 1 yield StopIteration result_2 = 0 if result_1: # Pre stage 2 result_2 = yield # Post stage 2 yield StopIteration for i in range(result_2): # Pre stage 3 result_3 = yield # Post stage 3 yield StopIteration # Pre stage 4 result_4 = yield # Post stage 4 def use_multistage(): blk = multi_stage() block next_stage(blk): # Stage 1 continue val_1 block next_stage(blk): # Stage 2 continue val_2 block next_stage(blk): # Stage 3 continue val_3 block next_stage(blk): # Stage 4 continue val_4 Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.skystorm.net
Nick Coghlan wrote:
With an appropriate utility block manager
I've just thought of another potential name for them: Block Utilization and Management Function (BUMF) :-) -- Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg.ewing@canterbury.ac.nz +--------------------------------------+
participants (4)
-
Greg Ewing
-
Guido van Rossum
-
Nick Coghlan
-
Nick Coghlan