zer0uno
zer0uno

Reputation: 8030

yield extended syntax and send method

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

Answers (1)

Martijn Pieters
Martijn Pieters

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 with send() and any exceptions passed in with throw() are passed to the underlying iterator if it has the appropriate methods. If this is not the case, then send() will raise AttributeError or TypeError, while throw() 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 raising StopIteration, 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

Related Questions