generator/coroutine terminology
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Mon Mar 16 07:51:21 EDT 2015
Chris Angelico wrote:
> On Mon, Mar 16, 2015 at 7:36 PM, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
>> I expect that the interpreter can tell the difference between
>>
>> yield spam
>>
>> and
>>
>> x = yield spam
>>
>> and only allow send(), close() and throw() on generators which include
>> the second form. If it can't, then that's a limitation, not a feature to
>> be emulated.
>>
>
> Hmm. That would imply that an expression is different if it's used
> somewhere. In Python, there's no difference between these two function
> calls:
>
> x = func(1,2,3)
> func(1,2,3)
>
> except for the actual name binding.
That's a pretty big difference though :-)
Just for the record, it's not just name bindings that make a generator
behave as a coroutine. E.g.:
py> def co():
... print( (yield 1) + 2 )
...
py> a = co()
py> next(a)
1
py> a.send(98)
100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Not the most *useful* example, I grant, but it demonstrates the idea.
However, for simplicity let's ignore that and pretend it's only name
binding we care about.
> If a yield expression enabled
> send() if and only if its return value were used, then it would be
> extremely surprising:
It's surprising to me that you can send() into a non-coroutine and have it
ignored. I'm not even sure that is documented behaviour or just an accident
of implementation.
> def use_and_discard():
> while True:
> _ = (yield 1)
>
> def just_yield():
> while True:
> yield 1
>
> So... I can send into the first but not into the second?
That's the idea.
The second one is not a coroutine, and cannot be used as a coroutine in any
useful way. I maintain that it is a mistake that you can send() into it at
all, and if somebody does, it is invariably an error. That would be like
send()ing into None, or a str. We know what the Zen says about errors...
> That said, though, it would be quite reasonable for a *linter* to warn
> you about sending into a generator that never uses sent values. With a
> good type inference system (not sure if MyPy is sufficient here), it
> would be possible to distinguish between those two functions and
> declare that the first one has the return type "sendable_generator"
> and the second one "nonsendable_generator", and give a warning if you
> send into the latter. But I don't think that's the language's job.
I do :-)
I think it would be hard for a linter to do this. It can't just do a
type-check, because the types of generator-iterators and
generator-coroutines are the same. It would need to actually analyse the
source code, AST, or byte-code. That's doable, but the compiler already
does that, and compiles different code for the two cases:
py> from dis import dis
py> def g1():
... yield 1
...
py> def g2():
... x = yield 1
...
py> dis(g1)
2 0 LOAD_CONST 1 (1)
3 YIELD_VALUE
4 POP_TOP
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
py> dis(g2)
2 0 LOAD_CONST 1 (1)
3 YIELD_VALUE
4 STORE_FAST 0 (x)
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
so it should be easy for the compiler to recognise a yield used as if it
were a statement versus one used as an expression, and take appropriate
steps. But maybe there are subtle corner cases I haven't thought of.
--
Steven
More information about the Python-list
mailing list