[Python-Dev] Pre-PEP: Task-local variables
Nick Coghlan
ncoghlan at gmail.com
Thu Oct 20 14:40:07 CEST 2005
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 | ncoghlan at gmail.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.blogspot.com
More information about the Python-Dev
mailing list