Previously, you've learned some basics of generators. Let's revise the most important points. You already know that a generator is declared the same way as a regular function, with a single difference: the return keyword gets replaced with yield. A generator declares a function that behaves like an iterator and gives you a new value when you ask for it. Due to this, generators are memory-efficient: they only require memory for the one value they yield. Also, yield actually saves the state of the function, so that each time you ask the generator to produce a new value, execution continues from where it stopped. Now, it's high time we learned one more useful feature of the keyword yield. Let's see how yield from works and why it is useful.
Yield from expression
Suppose you have the following generator:
def generator():
for x in range(100):
yield x
for y in range(100, 200):
yield y
As you can see, this generator yields numbers from 0 to 199. However, what if you need to split it into two generators in order to reuse them later? You can rewrite the code the following way:
def generator2():
for x in range(100):
yield x
def generator3():
for y in range(100, 200):
yield y
def generator():
for x in generator2():
yield x
for y in generator3():
yield y
This generator() also yields the numbers from 0 to 199. However, it seems unnecessary and repetitive to iterate over both generator2 and generator3 once again and yield their values. That's where yield from comes in handy. Let's rewrite the code, using this expression:
def generator():
yield from generator2()
yield from generator3()
This version gives the same result, but the code looks much cleaner and is easier to write and read. In the code above, generator() is the delegating generator, while generator2 and generator3 are called sub-generators. So, by using yield from you can delegate a part of generator operations to a sub-generator. This allows you to divide the code of the main generator and put a part of it into another one. The syntax is also very easy:
yield from <expr>
Note that <expr> can be any iterable: list, dictionary, another generator, etc.
Why is it useful?
Basically, you can use yield from to make it easier to correctly iterate through and yield values from another iterable.
For simple iterators, yield from is equivalent to a for-loop, but it also contains the full range of generator features and allows generator code to be fractured in an easy and straightforward way.
def gen(iterable):
for i in list(iterable):
yield i
# is equivalent to
def gen2(iterable):
yield from list(iterable)
When the code execution comes to yield from <exp>, the iteration of <exp> starts and yielded values are sent to the outer generator right away. It goes on until <exp> is exhausted and returns StopIteration. After this, the execution of the outer generator continues.
Let's have a look at one more example that demonstrates this:
def one_more_generator(word):
yield from word
print(word)
When you call the code above, firstly, word will be iterated until the end, and then execution of the outer generator will go on:
for i in one_more_generator('python'):
print(i)
# output
p
y
t
h
o
n
python
Thus, the main benefit of using yield from <expression> is the opportunity to divide generators into several sub-generators. As a result, yield from makes coding easier and more efficient. If you are interested in seeing more examples of cases when and how yield from turns out useful, check out the PEP.
Summary
In this topic, you've learned the syntax of the yield from expression and have found out what a delegating generator and a sub-generator are. Let's sum it up:
a delegating generator is a generator in which the
yield from <expr>syntax appears;a sub-generator is a generator used in the
<expr>part of theyield from <expr>syntax;the
yield fromexpression allows dividing the generator into multiple sub-generators without extra rewriting;the
yield fromexpression allows yielding values from another iterable inside a generator;for simple iterators,
yield fromis equivalent to a for-loop syntax;the
yield fromexpression makes code easier and cleaner.
Let's practice all of this right away!