[Python-Dev] Pre-PEP: Task-local variables

Nick Coghlan ncoghlan at gmail.com
Thu Oct 20 15:25:48 CEST 2005


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   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.blogspot.com


More information about the Python-Dev mailing list