Nick Coghlan wrote: > 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.
I realised this isn't actually true for the version I posted, and the __with__ method example in the PEP - changes made to the decimal context in the "without" block would be visible after the "with" block. Consider the following: def iter_sin(iterable): # Point A with decimal.getcontext() as ctx: ctx.prec += 10 for r in iterable: y = sin(r) # Very high precision during calculation without: yield +y # Interim results have normal precision # Point B What I posted would essentially work for this example, but there isn't a guarantee that the context at Point A is the same as the context at Point B - the reason is that the thread-local context may be changed within the without block (i.e., external to this iterator), and that changed context would get saved when the decimal.Context context manager was resumed. To fix that, the arguments to StopIteration in __suspend__ would need to be used as arguments when the generator is recreated in __resume__. That is, the context manager would look like: @suspendable def __with__(self, oldctx=None): # Accept argument in __resume__ newctx = self.copy() if oldctx is None: oldctx = decimal.getcontext() decimal.setcontext(newctx) try: yield newctx finally: decimal.setcontext(oldctx) raise StopIteration(oldctx) # Return result in __suspend__ (This might look cleaner if "return arg" in a generator was equivalent to "raise StopIteration(arg)" as previously discussed) And (including reversion to 'one-use-only' status) the wrapper class would look like: class SuspendableGeneratorContext(object): def __init__(self, func, args, kwds): self.gen = func(*args, **kwds) self.func = func self.args = None def __with__(self): return self def __enter__(self): try: return self.gen.next() except StopIteration: raise RuntimeError("generator didn't yield") def __suspend__(self): try: self.gen.next() except StopIteration, ex: # Use the return value as the arguments for resumption self.args = ex.args return else: raise RuntimeError("generator didn't stop") def __resume__(self): if self.args is None: raise RuntimeError("context not suspended") self.gen = self.func(*args) try: self.gen.next() except StopIteration: raise RuntimeError("generator didn't yield") def __exit__(self, type, value, traceback): if type is None: try: self.gen.next() except StopIteration: return else: raise RuntimeError("generator didn't stop") else: try: self.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 Cheers, Nick. -- 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