from compat.functools import wraps as _wraps
from sys import exc_info as _exc_info
from weakref import WeakValueDictionary as _WeakValueDictionary
# use WeakValueDictionary to associate data with generator instances
# for alternative, wrapper-based solutions, see
# <http://code.activestate.com/recipes/164044/>
_yieldingfrom = _WeakValueDictionary()
class _shared_stack(object):
"""Stack whose top parts can also be stacks.
Sample usage:
>>> m = _shared_stack(); m.push("m", [])
>>> n = _shared_stack(m); m.push("n", []) # initialize n from m's head
>>> # m and n now have same head
>>> o = _shared_stack(n); n.push("o", []) # pushes to both
>>> p = _shared_stack(n)
>>> q = _shared_stack(); n.push("p", q.root())
>>> m._trace()
[] # q's root, head of m,n,o,p,q
['p', []] # p's root, top of m,n,o,p
['o', ['p', []]]
['n', ['o', ['p', []]]] # n's root
['m', ['n', ['o', ['p', []]]]] # m's root
>>> m._headstack()[0] is q.root()
True
"""
def __init__(self, data=None):
"""Initializing with another shared_stack
makes a stack with the same head."""
if data is None:
data = []
elif isinstance(data, _shared_stack):
data, _ = data._headstack()
# Implemented as a dict:
# {'head': head_list, list_id: previous_list, ...
# 'root': root_list, root_list_id: True}
self._stack = {'head': data, 'root': data, id(data): True}
def _headstack(self):
stack = self._stack
head = stack['head']
while True:
# pop levels until previous_list is not empty
head_id = id(head)
if stack[head_id]:
# root_list_id also points to a True value
break
stack['head'] = head = stack.pop(head_id)
while head:
# expand until previous_list's final element is empty
stack['head'] = next = head[-1]
stack[id(next)] = head
head = next
return head, stack
def root(self):
return self._stack['root']
def top(self):
head, stack = self._headstack()
top = stack[id(head)]
return (top if top is not True else None)
def push(self, *args):
head, _ = self._headstack()
head.extend(args)
def pop(self):
head, stack = self._headstack()
if head is stack['root']:
raise IndexError("pop from empty stack")
stack['head'] = prev = stack.pop(id(head))
del prev[:]
def __nonzero__(self):
head, stack = self._headstack()
return head is not stack['root']
def _trace(self):
head, stack = self._headstack()
print "Tracing 0x%x:" % (id(self),)
while True:
print '0x%x=%s' % (id(head), head)
head = stack[id(head)]
if head is True: break
def __repr__(self):
return '_shared_stack(%r)' % (self._shared_stack['root'],)
class _from(object):
def __init__(self, EXPR):
self.iterator = iter(EXPR)
def supergenerator(genfunct):
"""Implements PEP 380. Use as:
@supergenerator
def genfunct(*args):
try:
sent1 = (yield val1)
...
retval = yield _from(iterator)
...
except Exception, e:
# caller did generator.throw
pass
finally:
# closing
pass
"""
@_wraps(genfunct)
def wrapper(*args, **kwargs):
gen = genfunct(*args, **kwargs)
_yieldingfrom[gen] = gen_yf = _stack()
try:
# if first poll of gen raises StopIteration
# or any other Exception, we propagate
item = gen.next()
# OUTER loop
while True:
# yield _from(EXPR)
# semantics based on PEP 380, Revised**12, 19 April
if isinstance(item, _from):
_i, _iyf = item.iterator, None
try:
# first poll of the subiterator
_y = _i.next()
except StopIteration, _e:
# subiterator exhausted on first poll
# extract return value
_r = _e.args
if not _r: _r = (None,)
else:
# if subgenerator is another supergenerator
# extract the root of its gen_yf shared_stack
try:
_iyf = _i.gi_frame.f_locals['gen']
except (AttributeError,KeyError):
_iyf = []
else:
_iyf = _yieldingfrom.get(_iyf,[])
if isinstance(_iyf, _stack):
_iyf = _iyf.root()
gen_yf.push(_i, _iyf)
# INNER loop
while True:
if _iyf is None:
# subiterator was adopted by caller
# and is now exhausted
item = _y
break
try:
# yield what the subiterator did
_s = (yield _y)
except GeneratorExit, _e:
# close as many subiterators as possible
while gen_yf:
_i, _iyf = gen_yf.top()
try:
_close = _i.close
except AttributeError:
pass
else:
_close()
gen_yf.pop()
# finally clause will gen.close()
raise _e
except BaseException:
# caller did wrapper.throw
_x = _exc_info()
# throw to the subiterator if possible
while gen_yf:
_i, _iyf = gen_yf.top()
try:
_throw = _i.throw
except AttributeError:
# doesn't attempt to close _i?
# try throwing to subiterator's parent
pass
else:
try:
_y = _throw(*_x)
except StopIteration, _e:
_r = _e.args
if not _r: _r = (None,)
# drop to INTERSECTION A
# then to INTERSECTION B
break
else:
_r = None
# drop to INTERSECTION A
# then to INNER loop
break
gen_yf.pop()
else:
# if gen raises StopIteration
# or any other Exception, we propagate
_y = gen.throw(*_x)
_r = _iyf = None
# fall through to INTERSECTION A
# then to INNER loop then to OUTER loop
pass
# INTERSECTION A
# restart INNER loop or proceed to B?
if _r is None: continue
# caller did send/next
else:
if not gen_yf:
# subiterator was adopted by caller
# and is now exhausted
_r = (_s,)
_iyf = None
# fall through to INTERSECTION B
pass
else:
if _iyf:
# check if current _i itself
# now yielding from a subiterator?
_i, _iyf = gen_yf.top()
try:
# re-poll the subiterator
if _s is None:
_y = _i.next()
else:
_y = _i.send(_s)
except StopIteration, _e:
# subiterator is exhausted
# extract return value
_r = _e.args
if not _r: _r = (None,)
# fall through to INTERSECTION B
pass
else:
# restart INNER loop
continue
# INTERSECTION B
# done yielding from active subiterator
# send retvalue to its parent
while True:
if _iyf is not None:
gen_yf.pop()
if gen_yf:
_i, _iyf = gen_yf.top()
try:
# push retval to subiterator's parent
_y = _i.send(_r[0])
except StopIteration, _e:
# parent is exhausted, try _its_ parent
_r = _e.args
if not _r: _r = (None,)
continue
else:
# fall through to INTERSECTION C
# then to INNER loop
pass
else:
# gen_yf is empty, push return value to gen
# if gen raises StopIteration
# or any other Exception, we propagate
_y = gen.send(_r[0])
_iyf = None
# fall through to INTERSECTION C
# then to INNER loop then to OUTER loop
pass
# INTERSECTION C
# passed retvalue, continue INNER loop
break
# traditional yield from gen
else:
try:
sent = (yield item)
except Exception:
# caller did wrapper.throw
_x = _exc_info()
# if gen raises StopIteration
# or any other Exception, we propagate
item = gen.throw(*_x)
else:
# if gen raises StopIteration
# or any other Exception, we propagate
item = gen.send(sent)
# end of OUTER loop, restart it
pass
finally:
# gen raised Exception
# or caller did wrapper.close()
# or wrapper was garbage collected
gen.close()
return wrapper