Detailed Explanation of Python yield

  • 2021-12-05 06:41:33
  • OfStack

A summary of another example of how to generate Fibonacci number sequence return

How to Generate Fibonacci Sequence

Fibonacci (Fibonacci) sequence is a very simple recursive sequence. Except for the first and second numbers, any number can be obtained by adding the first two numbers. It is a very simple problem to output the first N number of Fibonacci series with computer program. Many beginners can easily write the following functions:

Listing 1. Simple output N number before Fibonacci number sequence

Instances


#!/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)

By executing the above code, we can get the following output:

1
1
2
3
5

The result is no problem, but experienced developers will point out that printing numbers with print directly in fab function will lead to poor reusability of this function, because 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 out the sequence directly, but to return an List. The following is the second version of the fab function after rewriting:

Listing 2. Outputting the number of N before the Fibonacci number sequence, version 2

Instances


#!/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

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

1
1
2
3
5

The rewritten fab function can meet the reusability requirements by returning List, but more experienced developers will point out that the memory occupied by this function in operation will increase with the increase of max parameter. If you want to control the memory occupation, it is best not to use List

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

Listing 3. Iterating through an iterable object


for i in range(1000): pass

Causes a 1000-element List to be generated, and the code:


for i in xrange(1000): pass

Instead of generating a 1, 000-element List, the next 1 value is returned in each iteration, with a small memory footprint. Because xrange does not return List, it returns an iterable object.

With iterable, we can rewrite the fab function into an class that supports iterable. The following is the third version of Fab:

Listing 4. Version 3

Instances


#!/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 of the sequence through next (), and the memory footprint is always constant:

1
1
2
3
5

However, with this version of class rewritten, the code is far less concise than the fab function in version 1. yield comes in handy if we want to keep the simplicity of the fab function in version 1 and get the effect of iterable at the same time:

Listing 5. Using version 4 of yield

Instances


#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b      #  Use  yield
        # print b 
        a, b = b, a + b 
        n = n + 1
for n in fab(5): 
    print n

Compared with the first version, the fourth version of fab only changed print b into yield b, which achieved the effect of iterable while maintaining simplicity.

Calling version 4 of fab and version 2 of fab completely 1 to:

1
1
2
3
5

Simply put, the function of yield is to change a function into an generator, the function with yield is no longer an ordinary function, and the Python interpreter will treat it as an generator. Calling fab (5) will not execute fab function, but return an iterable object! When the for loop is executed, the code inside the fab function is executed every time. When the yield b is executed, the fab function returns an iteration value. When the next iteration is executed, the code continues to execute from the next statement of yield b, and the local variable of the function looks exactly the same as before the last interrupt execution, so the function continues to execute until it encounters yield again.

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

Listing 6. Execution process


>>>f = fab(5) 
>>> f.next() 
1 
>>> f.next() 
1 
>>> f.next() 
2 
>>> f.next() 
3 
>>> f.next() 
5 
>>> f.next() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
StopIteration

When the function is finished, generator automatically throws an StopIteration exception to indicate that the iteration is complete. In an for loop, there is no need to handle an StopIteration exception, and the loop ends normally.

We can draw the following conclusions:

A function with an 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 () (next () is automatically called in the for loop). Although the execution process still follows the process of the function, it is interrupted every time it executes to one yield statement, and an iteration value is returned, and the execution continues from the next yield statement at the next execution. It looks as if a function is interrupted several times by yield during normal execution, and each interrupt returns the current iteration value through yield.

The benefits of yield are obvious. Rewriting a function into an generator obtains iterative ability. Compared with using an instance of a class to save the state to calculate the value of the next next (), it is not only simple in code, but also extremely clear in execution flow.

How to determine whether a function is a special generator function? isgeneratorfunction can be used to determine:

Listing 7. Judgment using isgeneratorfunction


>>>from inspect import isgeneratorfunction 
>>> isgeneratorfunction(fab) 
True

Attention should be paid to distinguishing fab from fab (5), fab is an generator function, and fab (5) is an generator returned by calling fab, just like the difference between class definition and class instance:

Listing 8. Class definition and class instance


>>>import types 
>>> isinstance(fab, types.GeneratorType) 
False 
>>> isinstance(fab(5), types.GeneratorType) 
True

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 generator instance that does not affect each other:


#!/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
0

Role of return

In one generator function, if there is no return, the function is executed by default, and if return is executed, StopIteration is directly thrown to terminate the iteration.

Another example

Another example of yield comes from file reading. Calling the read () method directly on a file object can result in an unpredictable footprint. A good way is to use a fixed-length buffer to continuously read the contents of the file. With yield, we can easily read files without writing iterative classes to read files:

Listing 9. Another example of yield

Instances


#!/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
1

The above has only briefly introduced the basic concepts and usage of yield, and yield has more powerful usage in Python 3, which we will discuss in a future article.

Note: The code in this article was debugged in Python 2.7

Summarize

This article is here, I hope to give you help, but also hope that you can pay more attention to this site more content!


Related articles: