Python language basics 64: a basic example of yield generators
November 15, 2015 Leave a comment
Introduction
In the previous post we looked at the continue keyword in Python. We saw how it helped us stop the execution of a loop in case the current value of the iteration wasn’t to be handled in the same way as others.
In this post we’ll look at something completely different: the somewhat odd language construct of yielding a value.
The English word ‘yield‘ means approximately ‘produce’ or ‘give way’. A good investment can yield a high return. A fertile field can yield a lot of corn.
Yielding in Python
The yield keyword in Python is similar in usage. It is used in conjunction with loops that are lazily evaluated, where lazy means ‘on demand’ so that no memory is occupied unless the function is evaluated by the caller. Also, the function that yields a value – called a generator function – “remembers” the state from the previous execution. This all may sound mysterious. The usage of the yield keyword is definitely confusing at first I think.
C# has also a yield keyword and it works similar to how it does in Python. I’m not aware of any equivalent in Java.
You can think of generator functions as collections, or iterables. Iterables are objects that can be iterated, just like collections, in a for-each loop. A function which has the yield keyword automatically becomes a generator function that can be called in a for-each loop. Generator functions cannot be called and executed like “normal” functions.
Here’s a normal integer list with an iterator:
integers = [1, 2, 3, 4] for i in integers: print('Printing ', i)
Here’s the output:
Printing 1
Printing 2
Printing 3
Printing 4
Here’s the generator function equivalent of the above code:
def integer_yielder(): yield 1 yield 2 yield 3 yield 4 integers = integer_yielder() for i in integers: print('Printing ', i)
We have a generator function with 4 yield statements. You can think of yield statements really as yield return statements. The above generator function has 4 yield return statements. It will return 1 in the first iteration and “remember” that it has already returned 1. In the next iteration it returns 2 and so on until there are no more elements to be “yielded”.
Notice how we assign integer_yielder to a variable just like we assigned an integer list to it previously. The key difference is that the integer list was loaded into memory at the moment of the variable assignment. Generator functions on the other hand are evaluated lazily in a just-in-time manner, meaning that the integers 1, 2, 3 and 4 yielded by the function are not loaded into memory. First only ‘1’ is loaded when the iteration starts, and then 2, 3 and finally 4.
This second version has the exact same printout as the first.
In the next post we’ll see how yield generators can keep the state of their variables from one iteration to another.
Read all Python-related posts on this blog here.