Today we’ll see how to use the yield from statement with generators.
If you haven’t read my previous three articles on generators (basics of generators, sending objects to generators and the throw method), it’s definitely recommended to do so before you go on with this one.
Using the yield from Statement
And now let’s have a look at the yield from statement.
Inside the body of a generator function we may use the yield from statement. It must be followed by an expression that evaluates to an iterable, from which an iterator will be returned.
By the way, here’s my article on iterables and iterators if you need to refresh your memory.
First let’s have a look at a typical generator:
>>> def char_generator():
... for char in "Hello":
... yield char
...
>>> it = char_generator()
>>>
>>> next(it)
'H'
>>> next(it)
'e'
>>> next(it)
'l'
Now we’ll rewrite it with a yield from statement:
>>> def char_generator():
... yield from "Hello"
...
>>> it = char_generator()
>>>
>>> next(it)
'H'
>>> next(it)
'e'
>>> next(it)
'l'
These two generators generate the same output. In the yield from version the expression is “Hello”, which evaluates to an iterable.
yield from Followed by Generator
The expression following yield from may be a generator, too.
Let’s define two generators:
>>> def first_parts():
... for word in ["intelligent", "strawberry", "concatenate", "listening"]:
... yield word[:5]
...
>>> def doubles():
... for number in range(5):
... yield number * 2
...
Now let’s use the two generators in another generator. First, let’s use them in a generator without the yield from statement:
>>> def words_and_numbers():
... for word in first_parts():
... yield word
... for number in doubles():
... yield number
...
And now let’s use them as expressions in the yield from statement.
>>> def words_and_numbers_again():
... yield from first_parts()
... yield from doubles()
...
And now let’s see how we can use our generators:
>>> words_from_first_parts = [item for item in first_parts()]
>>> print(words_from_first_parts)
['intel', 'straw', 'conca', 'liste']
>>> numbers_from_doubles = [item for item in doubles()]
>>> print(numbers_from_doubles)
[0, 2, 4, 6, 8]
The generators words_and_numbers and words_and_numbers_again return the same elements. We’re using list comprehensions to make lists of those elements.
>>> items_from_words_and_numbers = [item for item in words_and_numbers()]
>>> print(items_from_words_and_numbers)
['intel', 'straw', 'conca', 'liste', 0, 2, 4, 6, 8]
>>> items_from_words_and_numbers_again = [item for item in words_and_numbers_again()]
>>> print(items_from_words_and_numbers_again)
['intel', 'straw', 'conca', 'liste', 0, 2, 4, 6, 8]
It turns out that these two lists are equal:
>>> print(items_from_words_and_numbers == items_from_words_and_numbers_again)
True
The output we just saw visualizes that if we use a generator as the expression after yield from, it works exactly the same as if we had the subgenerator’s code in this place.
the return Statement
If there’s a return statement in the subgenerator that returns a value, the returned value will become the value of the yield from expression. Here’s an example:
>>> def subgenerator():
... yield 1
... yield 2
... return "hi there!"
...
>>> def generator():
... n = yield from subgenerator()
... print(n)
...
>>> for x in generator():
... print(x)
...
1
2
hi there!
The value returned by the subgenerator is ”hi there!”. In the generator this value is assigned to n and printed out.
Here’s the video version of the article: