Phillip J. Eby wrote: > This is still rather rough, but I figured it's easier to let everybody fill > in the remaining gaps by arguments than it is for me to pick a position I > like and try to convince everybody else that it's right. :) Your feedback > is requested and welcome.
I think you're actually highlighting a bigger issue with the behaviour of "yield" inside a "with" block, and working around it rather than fixing the fundamental problem. The issue with "yield" causing changes to leak to outer scopes isn't limited to coroutine style usage - it can happen with generator-iterators, too. What's missing is a general way of saying "suspend this context temporarily, and resume it when done". An example use-case not involving 'yield' at all is the "asynchronise" functionality. A generator-iterator that works in a high precision decimal.Context(), but wants to return values from inside a loop using normal precision is another example not involving coroutines. The basic idea would be to provide syntax that allows a with statement to be "suspended", along the lines of: with EXPR as VAR: for VAR2 in EXPR2: without: BLOCK To mean: abc = (EXPR).__with__() exc = (None, None, None) VAR = abc.__enter__() try: for VAR2 in EXPR2: try: abc.__suspend__() try: BLOCK finally: abc.__resume__() except: exc = sys.exc_info() raise finally: abc.__exit__(*exc) To keep things simple, just as 'break' and 'continue' work only on the innermost loop, 'without' would only apply to the innermost 'with' statement. Locks, for example, could support this via: class Lock(object): def __with__(self): return self def __enter__(self): self.acquire() return self def __resume__(self): self.acquire() def __suspend__(self): self.release() def __exit__(self): self.release() (Note that there's a potential problem if the call to acquire() in __resume__ fails, but that's no different than if this same dance is done manually). Cheers, Nick. P.S. Here's a different generator wrapper that could be used to create a generator-based "suspendable context" that can be invoked multiple times through use of the "without" keyword. If applied to the PEP 343 decimal.Context() __with__ method example, it would automatically restore the original context for the duration of the "without" block: class SuspendableGeneratorContext(object): def __init__(self, func, args, kwds): self.gen = None self.func = func self.args = args self.kwds = kwds def __with__(self): return self def __enter__(self): if self.gen is not None: raise RuntimeError("context already in use") gen = self.func(*args, **kwds) try: result = gen.next() except StopIteration: raise RuntimeError("generator didn't yield") self.gen = gen return result def __resume__(self): if self.gen is None: raise RuntimeError("context not suspended") gen = self.func(*args, **kwds) try: gen.next() except StopIteration: raise RuntimeError("generator didn't yield") self.gen = gen def __suspend__(self): try: self.gen.next() except StopIteration: return else: raise RuntimeError("generator didn't stop") def __exit__(self, type, value, traceback): gen = self.gen self.gen = None if type is None: try: gen.next() except StopIteration: return else: raise RuntimeError("generator didn't stop") else: try: gen.throw(type, value, traceback) except (type, StopIteration): return else: raise RuntimeError("generator caught exception") def suspendable_context(func): def helper(*args, **kwds): return SuspendableGeneratorContext(func, args, kwds) return helper -- Nick Coghlan | [EMAIL PROTECTED] | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.blogspot.com _______________________________________________ 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