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