Through the list generation formula, we can directly create a list. However, due to memory constraints, the list capacity must be limited. Moreover, creating a list containing 1 million elements not only takes up a lot of storage space, but if we only need to access the first few elements, the space occupied by most of the latter elements is wasted.
Therefore, if the list elements can be calculated according to some algorithm, can we continuously calculate the subsequent elements in the process of circulation? This saves a lot of space by eliminating the need to create a complete list. In Python, this mechanism of calculating while looping is called generator.
There are many ways to create a generator. The first method is very simple. Just change [] of a list generation formula to (), and a generator is created:
L = [s for s in range(10)] print("L:",L) G = (s for s in range(10)) print("G:",G) '''Output results: L: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] G: <generator object <genexpr> at 0x000001A42D9AECF0> '''
The only difference between creating L and g is that the outermost [] and (), l is a list and g is a generator.
We can print every element of the list directly, but how can we print every element of the generator?
If you want to print one by one, you can get the next return value of the generator through the next() function:
G = (s for s in range(3)) print(next(G)) print(next(G)) print(next(G)) '''Output: 0 1 2 '''
If you output the next() function again, an error message will be prompted,
''' File "D:/Python/PythonFiles/test1/Plan2/Demo4.py", line 10, in <module> print(next(G)) StopIteration'''
The generator saves the algorithm. Each time next(g) is called, it calculates the value of the next element of G until the last element is calculated. When there are no more elements, it throws the StopIteration error.
The correct method is to use the for loop, because the generator is also an iteratable object:
G = (s for s in range(3)) for i in G: print(i) '''Output: 0 1 2 '''
Therefore, after we create a generator, we basically never call next(), but iterate it through the for loop, and we don't need to care about the StopIteration error.
generator is very powerful. If the calculation algorithm is complex and cannot be realized with a for loop similar to list generation, it can also be realized with functions.
For example, in the famous Fibonacci sequence, except the first and second numbers, any number can be obtained by adding the first two numbers:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
The Fibonacci sequence can't be written in the list generation formula, but it's easy to print it with a function:
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done' print(fib(6)) '''Output: 1 1 2 3 5 8 done '''
Attention, assignment statement:
a, b = b, a + b
amount to:
t = (b, a + b) # t is a tuple a = t[0] b = t[1]
However, it is not necessary to explicitly write out the temporary variable t to assign a value.
Understand the yield keyword: Detailed explanation of yield in python -- the simplest and clearest explanation_ mieleizhi0522 blog - CSDN blog_ yield
It can be seen that fib function actually defines the calculation rules of fiboracci sequence. You can start from the first element and calculate any subsequent elements. This logic is actually very similar to generator.
In other words, the above function is only one step away from the generator. To change fib function into generator function, just change print(b) to yield b:
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' max = (x for x in fib(6)) for n in max: print(n) '''Output results: 1 1 2 3 5 8 '''
This is another way to define the generator. If a function definition contains the yield keyword, the function is no longer an ordinary function, but a generator function. Calling a generator function will return a generator:
print(fib(6)) '''output <generator object fib at 0x00000276B4B3FEB0> '''
Here, the most difficult thing to understand is that the execution process of generator function is different from that of ordinary function. Ordinary functions are executed sequentially. They return when they encounter a return statement or the last line of function statements. The function that becomes the generator is executed every time next() is called. If the yield statement returns, it will continue to execute from the last returned yield statement when it is executed again.
However, when calling the generator with the for loop, it is found that the return value of the return statement of the generator cannot be obtained. If you want to get the return value, you must catch the StopIteration error. The return value is contained in the value of StopIteration:
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' #This sentence cannot be output '''Want to get return The return value of the statement must be captured StopIteration error The return value is contained in StopIteration of value Medium: ''' g = fib(6) while True: try: x = next(g) print("g:",x) except StopIteration as e: print('Generator return value:',e.value) break '''Output results: g: 1 g: 1 g: 2 g: 3 g: 5 g: 8 Generator return value: done '''
After the above bold words are followed: (the function that becomes the generator is executed every time next() is called. If the yield statement returns, it will continue to execute from the last returned yield statement when it is executed again.)
For a simple example, define a generator function and return the numbers 1, 2 and 3 in turn:
When calling the generator function, first generate a generator object, and then use the next() function to continuously obtain the next return value:
def odd(): print("step1") yield 1 print("step2") yield 2 print("step3") yield 3 o = odd() print(next(o)) print(next(o)) print(next(o)) print(next(o)) '''Output: step1 1 step2 2 step3 3 print(next(o)) StopIteration '''
It can be seen that odd is not an ordinary function, but a generator function. In the process of execution, it will be interrupted when it encounters yield and continue to execute next time. After the yield is executed three times, no yield can be executed. Therefore, an error is reported when next(o) is called for the fourth time.
Please note that calling the generator function will create a generator object, and multiple calls to the generator function will create multiple independent generators.
def odd(): print("step1") yield 1 print("step2") yield 2 print("step3") yield 3 print(next(odd())) print(next(odd())) print(next(odd())) '''Output: step1 1 step1 1 step1 1 step1 1 '''
This is because each time the odd function is called, a generator object is created. The above code creates three completely independent generator objects. Calling next() on the three generators will return the first value
The correct way to write this is to create a generator object, and then call next() on this generator object
def odd(): print("step1") yield 1 print("step2") yield 2 print("step3") yield 3 od = odd() for i in od: print(i) '''Output: step1 1 step2 2 step3 3 '''
practice:
Yang Hui triangle It is defined as follows:
1 / \ 1 1 / \ / \ 1 2 1 / \ / \ / \ 1 3 3 1 / \ / \ / \ / \ 1 4 6 4 1 / \ / \ / \ / \ / \ 1 5 10 10 5 1
Treat each line as a list, try to write a generator, and continuously output the list of the next line:
def triangles(): L = [1] while True: yield L L = L + [0] L = [L[i] + L[i -1] for i in range (len(L))] n = 0 results = [] for t in triangles(): results.append(t) n = n + 1 if n == 10: break for t in results: print(t) if results == [ [1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], [1, 5, 10, 10, 5, 1], [1, 6, 15, 20, 15, 6, 1], [1, 7, 21, 35, 35, 21, 7, 1], [1, 8, 28, 56, 70, 56, 28, 8, 1], [1, 9, 36, 84, 126, 126, 84, 36, 9, 1] ]: print('Test passed!') else: print('Test failed!') '''Output results: [1] [1, 1] [1, 2, 1] [1, 3, 3, 1] [1, 4, 6, 4, 1] [1, 5, 10, 10, 5, 1] [1, 6, 15, 20, 15, 6, 1] [1, 7, 21, 35, 35, 21, 7, 1] [1, 8, 28, 56, 70, 56, 28, 8, 1] [1, 9, 36, 84, 126, 126, 84, 36, 9, 1] Test passed! '''