Reputation: 8030
I read about yield
extended syntax, so that if I have:
def numgen(N):
for i in range(N):
n = yield i
if n:
yield n
I can factor it:
def numgen(N):
n = yield from range(N)
if n:
yield n
but I have noticed that if I do, after I have coded the second generator:
g = numgen(10)
next(g)
g.send(54)
I get the following error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in gensquare
AttributeError: 'range_iterator' object has no attribute 'send'
So, how is that? How can I send a value to my numgen
generator object?
Upvotes: 2
Views: 284
Reputation: 1123830
range()
is not a generator, it doesn't have a generator.send()
method.
This is clearly documented in the yield
expression documentation:
When
yield from <expr>
is used, it treats the supplied expression as a subiterator. All values produced by that subiterator are passed directly to the caller of the current generator’s methods. Any values passed in withsend()
and any exceptions passed in withthrow()
are passed to the underlying iterator if it has the appropriate methods. If this is not the case, thensend()
will raiseAttributeError
orTypeError
, whilethrow()
will just raise the passed in exception immediately.
Emphasis mine.
You are trying to send a value to the range()
iterator, but it has no .send()
method.
range()
is just a sequence, not a generator object; you can create multiple iterators for it, you can test if a number is a member of the sequence, ask it for its length, etc.
Note that your 'refactoring' is not the same thing at all; in your original n
is assigned anything you send in through generator.send()
; in your second version yield from
returns the value
attribute of the StopIteration
exception raised when the sub-iterator ends. If the sub-iterator is a generator itself, you can set that value either by manually raising StopIteration(value)
or by using a return
statement. yield from
cannot return the value sent in with generator.send()
because such values would be passed on to the sub-generator instead.
Again, from the documentation:
When the underlying iterator is complete, the value attribute of the raised
StopIteration
instance becomes the value of the yield expression. It can be either set explicitly when raisingStopIteration
, or automatically when the sub-iterator is a generator (by returning a value from the sub-generator).
So your first version is set up to receive N
messages, yielding both the i
for
target and any sent value is true-thy, while the other passes on any sent messages to a degelated-to generator, then would yield just the StopIteration
value if it is true-thy once, after the delegated-to iterator is done.
Upvotes: 5