[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