A Brief Analysis of the Use of Python's yield
Category Programming Techniques
You may have heard that a function with yield in Python is called a generator. What is a generator?
Let's put aside the concept of a generator for now and use a common programming problem to illustrate the concept of yield.
How to Generate the Fibonacci Sequence
The Fibonacci sequence is a very simple recursive sequence. Except for the first and second numbers, any number can be obtained by adding the previous two numbers. Outputting the first N numbers of the Fibonacci sequence with a computer program is a very simple problem, and many beginners can easily write the following function:
Listing 1. Simple Output of the First N Numbers of the Fibonacci Sequence
Example
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a + b
n = n + 1
fab(5)
Executing the above code, we get the following output:
1
1
2
3
5
The result is correct, but experienced developers will point out that directly printing numbers in the fab function will lead to poor reusability of the function, because the fab function returns None, and other functions cannot obtain the sequence generated by this function.
To improve the reusability of the fab function, it is best not to print the sequence directly, but to return a List. The following is the second version of the rewritten fab function:
Listing 2. Output the First N Numbers of the Fibonacci Sequence, Second Edition
Example
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
n, a, b = 0, 0, 1
L = []
while n < max:
L.append(b)
a, b = b, a + b
n = n + 1
return L
for n in fab(5):
print n
The List returned by the fab function can be printed in the following way:
1
1
2
3
5
The rewritten fab function meets the requirements of reusability by returning a List, but more experienced developers will point out that the memory occupied by this function will increase with the increase of the parameter max. If you want to control the memory occupation, it is best not to use a List to save intermediate results, but to iterate through an iterable object. For example, in Python2.x, the code:
Listing 3. Iteration through an iterable object
for i in range(1000): pass
will generate a List of 1000 elements, while the code:
for i in xrange(1000): pass
will not generate a List of 1000 elements, but will return the next value in each iteration, occupying very little memory space. Because xrange does not return a List, but returns an iterable object.
Using iterable, we can rewrite the fab function as a class that supports iterable, which is the third version of Fab:
Listing 4. Third Version
Example
#!/usr/bin/python
# -*- coding: UTF-8 -*-
class Fab(object):
def __init__(self, max):
self.max = max
self.n, self.a, self.b = 0, 0, 1
def __iter__(self):
return self
def next(self):
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()
for n in Fab(5):
print n
The Fab class continuously returns the next number in the sequence through next(), and the memory occupation is always constant:
1
1
2
3
5
However, this version rewritten using class is far less concise than the first version of the fab function. If we want to maintain the conciseness of the first version of the fab function while also obtaining the effect of iterable, yield comes into play:
Listing 5. Fourth Edition Using yield
Example
```
!/usr/bin/python
-- coding: UTF-8 --
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b # Using yield # print b