How To Use “yield” in Python?. Python Generator — from basic to... | by Christopher Tao | Towards Data Science

If you’re a Python developer, I believe that you must know the Generator in Python. The key to defining a Python generator is to use the “yield” keyword. The Python generator is ubiquitously used in scenarios when we need a large collection, improve the readability of our code and other certain scenarios such as multi-threading.
You may or may not know how to use the “yield” keyword correctly yet. In this article, I’ll introduce the Python generator from basic usage. Some more advanced usage patterns will also be covered in the later sections.
Let’s make up an example in this tutorial. Suppose we have a group of people in our company who needs to be on call one by one. So, it will be a rotating roster.
names = ['Alice', 'Bob', 'Chris', 'David', 'Emily']
Now, we want to define a Python generator from this name list. The code will be as follows.
def gen_roster(names):    for name in names:        yield name
Please be noticed that we need to use the yield keyword rather than return, so we can use this function to make a generator.
roster = gen_roster(names)
Yes, that’s it. If you want to check the type of the variable roster, here is the result.
Now, we have got the roster. A Python generator is also iterable, so we can put it in a for-loop to get all the names in one go.
for name in roster:    print(name)
All right, it doesn’t make too much sense to use a generator in a for-loop in the previous example. The benefit of using a generator is that we can get one value at one time. This could be very useful when we are dealing with a huge collection. That is, when we create a generator, the items will not be read into the memory. Only if when we are trying to get the next item and hit the yield keyword, the item is generated.
Therefore, by all means, it is important to get the “next” element of the generator. In this case, we can use its __next__() function.
roster.__next__()
In fact, we can also do it as follows, which is more intuitive and easy to remember.
next(roster)
Have you noticed that the “cursor” was moved when we tried to get the “next” item? Remember we have defined a for-loop inside the generator’s definition? We can consider that the for-loop will be executed once a time until it reaches the keyword yield.
That also means, our “cursor” is at the 3rd name at the moment. So, if we try to output all the rest items, “Alice” and “Bob” will be skipped.
Now, the “cursor” is at the end. If we try to get the next item, an error will be thrown because there is no more item.
This is not actually what we want, because the roster will be used to decide who is on-call for the current rotation. In other words, we want the “cursor” to go back to the beginning so that all the names will be looped again.
The trick is simply putting a while True statement above the for-loop. So, once all the names are used up, the for-loop will start over again.
def gen_roster(names):    while True:        for name in names:            yield name
Now, let’s say we want to get a roster with 12 names. We only have 5 employees. So, they will be rotated.
for i in range(12):    print(next(roster))
If we don’t put the number “12”, the generator can keep generating more names indefinitely.
One advanced usage of the generator is to send a value to the generator. This value will become the result of the current yield expression, and the method returns the next value yielded by the generator.
So, don’t expect the generator to return the value we have just sent because it will return the next one. However, we can use this to do something inside the generator. For example, we can stop an infinite generator by sending a certain value into it.
def gen_roster(names):    while names:        for name in names:            current_name = yield name            if current_name == 'stop':                names = None                break
The generator has been defined to terminate the loop if a value “stop” is sent from outside. So, we can verify the behaviour as follows.
roster = gen_roster(names)for i in range(10):    if i == 3:        roster.send('stop')    print(next(roster))
In the code above, we ask the program to loop 10 times. However, for the 4th round, we pass the value “stop” into the generator. As a result, only 3 names were output and the generator has stopped at the 4th round rather than being looped 10 times.
The send method will be pretty useful when we want to change the behaviour or rules of a generator in a multi-threading programming scenario.
When there is something wrong, we can use the throw() method to raise an exception at the point where the generator was paused. We can customise the error type. For demo purposes in this tutorial, I will simply use a “TypeError” for convenience.
roster = gen_roster(names)next_name = roster.throw(TypeError, 'Stop!')
If nothing went wrong, but we still want to terminate the generator, we can use the close() method of a generator. This would be quite useful when we have an infinite generator and we want to stop it at a certain point.
roster = gen_roster(names)for i in range(10):    if i == 3:        roster.close()    print(next(roster))
The code above closed the generator “roster” at the 4th round, so when the next loop tried to get the next value, the exception was thrown.
In this article, I have introduced one of the most important and “Pythonic” concepts in Python — the generator. The usage of the keyword yield is the key to the Python generator.
Not only the basic usage is introduced, some advanced usage such as send a value to the generator from outside, raise an exception and close a generator were also covered. Hope it helps you to understand more about Python generator.
medium.com
If you feel my articles are helpful, please consider joining Medium Membership to support me and thousands of other writers! (Click the link above)