The usage of the yield function in Python is preliminarily resolved

  • 2020-05-05 11:27:37
  • OfStack

You may have heard that a function with yield is called generator (generator) in Python. What is generator?

Instead of generator, let's show the concept of yield in a common programming problem.

How does generate Fibonacci

The Fibonacci (Fibonacci) list is a very simple recursive sequence of Numbers. Any number except the first and second can be obtained by adding the first two Numbers together. Using a computer program to output the number of previous N listed in Fibonacci is a very simple problem, and many beginners can easily write the following function:

Listing 1. Simply output Fibonacci listing the number of N before
 


def fab(max):
 n, a, b = 0, 0, 1
 while n < max:
  print b
  a, b = b, a + b
  n = n + 1

By executing fab(5), we get the following output:
 


>>> fab(5)
1
1
2
3
5

The results are fine, but experienced developers will point out that printing Numbers directly with print in the fab function results in poor reusability because the fab function returns None, and other functions cannot get the array generated by the function.

To improve the reusability of the fab function, it is best not to print out the sequence directly, but to return an List. Here is the second version of the fab function:

Listing 2. Output Fibonacci listing the previous N Numbers for the second edition
 


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

You can print out List:
returned by the fab function as follows  


>>> for n in fab(5):
...  print n
...
1
1
2
3
5

The rewritten fab function satisfies the reusability requirement by returning List, but more experienced developers will point out that the amount of memory used by the function will increase as the parameter max increases, and it is best not to use List

if you want to control the memory usage

To save the intermediate results, but instead iterate through the iterable object. For example, in Python2.x, code:

Listing 3. Iterate over
with iterable objects  


for i in range(1000): pass

This causes a 1000 element List to be generated, and the code:
 


for i in xrange(1000): pass

Instead of generating an List of 1000 elements, the next value is returned in each iteration with minimal memory footprint. Because xrange does not return List, it returns an iterable object.

With iterable we can rewrite the fab function as class that supports iterable. The following is the third version of Fab:
listing 4. Third version


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()

The Fab class keeps returning the next number in the sequence through next(), and the footprint is always constant:
 


>>> for n in Fab(5):
...  print n
...

However, with this version of class, the code is not nearly as clean as the first version of fab. yield comes in handy if we want to keep the first version of the fab function clean while still getting the same effect as iterable:
Listing 5. fourth edition
using yield
 


def fab(max):
 n, a, b = 0, 0, 1
 while n < max:
  yield b
  # print b
  a, b = b, a + b
  n = n + 1
 
'''

The fourth version of fab, compared with the first version, only changed print b to yield b, which achieved the effect of iterable while keeping it simple.

Call version 4 of fab exactly the same as version 2 of fab:
 


>>> for n in fab(5):
...  print n
...


Simply put, what yield does is turn a function into an generator. A function with yield is no longer a normal function. The Python interpreter treats it as an generator. In for loop executes, each loop will execute fab functions within the code, and performs to yield b, fab function returns an iterator values, when the next iteration, code from yield b the next statement to continue the execution, and functions of the local variable seems to interrupt execution and the last is exactly the same as before, so the function to continue, until meet yield again.

You can also manually call the next() method of fab(5) (because fab(5) is an generator object that has the next() method), so we can see the fab execution flow more clearly:

Listing 6. Executing process
 


>>> fab(5)
1
1
2
3
5
0

When the function completes, generator automatically throws an StopIteration exception to indicate that the iteration is complete. In the for loop, without handling the StopIteration exception, the loop ends normally.

We can draw the following conclusions:

A function with yield is an generator, which, unlike normal functions, generates an generator that looks like a function call, but does not execute any function code until it is called next() (which is automatically called next() in the for loop). Although the execution process is still running as a function, each time an yield statement is executed, it breaks, returns an iteration, and continues from the next statement in yield the next time. It looks as if a function was interrupted several times during normal execution by yield, each interrupt returning the current iteration value through yield.

The benefits of yield are obvious. Rewriting a function to an generator gives you the ability to iterate, which is much cleaner than using an instance of the class to save state to calculate the value of the next next().

How do I determine if a function is a special generator function? isgeneratorfunction can be used to determine:

Listing 7. judge
with isgeneratorfunction
 


>>> fab(5)
1
1
2
3
5
1

Note the difference between fab and fab(5). fab is an generator function, while fab(5) is an generator returned from a call to fab, like the difference between a class definition and an instance of a class:

Listing 8. Definition of class and instance of class
 


>>> fab(5)
1
1
2
3
5
2

fab is not iterative, while fab(5) is iterative:
 


>>> from collections import Iterable
>>> isinstance(fab, Iterable)
False
>>> isinstance(fab(5), Iterable)
True

Each call to the fab function generates a new instance of generator, which is independent of each other:


>>> fab(5)
1
1
2
3
5
4

return

In an generator function, if there is no return, the function is executed by default until the end, and if return is executed, StopIteration is simply thrown to terminate the iteration.

another example

Another example of yield comes from file reading. If you call the read() method directly on a file object, it results in unpredictable memory usage. A good approach is to use a fixed-length buffer to read the contents of the file over and over again. With yield, we no longer need to write an iterative class to read the file, so we can easily read the file:

Listing 9. Another example of yield,
 


>>> fab(5)
1
1
2
3
5
5

This is just a brief introduction to the basic concepts and usage of yield. There are even more powerful USES of yield in Python 3, which we will discuss in a future article.

Note: the code in this article was debugged in Python 2.7 and passed


Related articles: