[Python-ideas] x=(yield from) confusion [was:Yet another alternative name for yield-from]
Jacob Holm
jh at improva.dk
Wed Apr 8 03:59:19 CEST 2009
Nick Coghlan wrote:
> Jacob Holm wrote:
>
>> So one @coroutine can't call another using yield-from. Why shouldn't it
>> be possible? All we need is a way to avoid the first next() call and
>> substitute some other value.
>>
>> [snip code]
> You can fix this without syntax by changing the way avg2 is written.
>
> [snip code]
>
> So it just becomes a new rule of thumb for coroutines: a yield-from
> friendly coroutine will accept a "start" argument that defaults to None
> and is returned from the first yield call.
>
That is not quite enough. Your suggested rule of thumb is to replace
def example(*args, **kw):
...
x = yield # first yield
with
def example(start=None, *args, **kw):
...
x = yield start # first yield
That would have been correct if my statement about what was needed
wasn't missing a bit. What you actually need to replace with is
something like:
def example(start, *args, **kw):
...
if 'throw' in start:
raise start['throw'] # simulate a throw() on first next()
elif 'send' in start:
x = start['send'] # simulate a send() on first next()
else:
x = yield start.get('yield') # use specified value for first next()
This allows you to set up so the first next() call skips the yield and
acts like a send() or a throw() was called. Actually, I think that can
be refactored to:
def cr_init(start):
if 'throw' in start:
raise start['throw']
if 'send' in start:
return start['send']
return yield start.get('yield')
def example(start, *args, **kw):
...
x = yield from cr_init(start)
Which makes it almost bearable.
It is also possible to write a @coroutine decorator that can be used
with this, the trick is to make the undecorated function available as an
attribute of the wrapper so it is available for use in yield-from. The
wrapper can also hide the existence of the start argument from top-level
users.
def coroutine(func):
def start(*args, **kwargs):
cr = func({}, *args, **kwargs)
v = cr.next()
if v is not None:
raise RuntimeError('first yield from coroutine was not None')
return cr
start.raw = func
return start
Using such a coroutine in yield-from then becomes:
# Yield None as first value
yield from example.raw({}, *args, **kwargs)
# Yield 42 as first value
yield from example.raw({'yield':42}, *args, **kwargs)
# Skip the first yield and treat the first next() as a send(42)
yield from example.raw({'send':42}, *args, **kwargs)
# Skip the first yield and treat the first next() as a throw(ValueError(42))
yield from example.raw({'throw':ValueError(42)}, *args, **kwargs)
While using it in other contexts is exactly like people are used to.
So it turns out a couple of support routines and a simple convention can
work around most of the problems with using @coroutines in yield-from.
I still think it would be nice if yield-from didn't insist on treating
its iterator as if it was new.
- Jacob
More information about the Python-ideas
mailing list