Yield generator is widely used in Python, but also the implementation principle of coroutine in Python, so it is necessary to learn it in depth.
This paper is divided into the following parts
- The use of simple yield
- Yield of empty
- yield from
- The send and yield values are assigned
- return yield
The use of simple yield
Here is an example of the simplest use of yield
def myfun(total):
for i in range(total):
yield i
Copy the code
Myfun is defined. When called, for example, myfun(4) returns a generator that can be called in three ways
1. Call next
>>> m = myfun(4)
>>> next(m)
0
>>> next(m)
1
>>> next(m)
2
>>> next(m)
3
>>> next(m)
Traceback (most recent call last):
File "<stdin>", line 1.in <module>
StopIteration
Copy the code
Each time next is called, the function executes until it stops at yield and waits for the next next, which will throw an exception if it exceeds the limit
2. Loop
for i in myfun(4):
print(i)
Copy the code
The run result is
0
1
2
3
Copy the code
Generators are iterable and can be called in a loop. A loop calls next as much as possible and returns the result of each next run
This is the most basic use of yield, so let’s apply it
The python built-in module Itertools implements some small generators, such as count, to do something like this
>>> from itertools import count
>>> c = count(start = 5, step = 2)
>>> next(c)
5
>>> next(c)
7
>>> next(c)
9
>>> next(c)
11
.
Copy the code
To achieve the following
def count(start, step = 1):
n = 0
while True:
yield start + n
n += step
Copy the code
3. Call next in the loop
Call next in the next loop, catch the exception with StopIteration and exit the loop
>>> m = myfun(4)
>>> while True:
. try:
. print(next(m))
. except StopIteration:
. break
.
0
1
2
3
Copy the code
Yield of empty
The yield null acts as an interrupter where the loop is interrupted to assist the execution of other programs. The return value is None. Let’s look at this example
>>> def myfun(total):
. for i in range(total):
. print(i + 1)
. yield
.
>>> a = myfun(3)
>>> next(a)
1
>>> next(a)
2
>>> next(a)
3
>>> next(a)
Traceback (most recent call last):
File "<stdin>", line 1.in <module>
StopIteration
>>> a = myfun(3)
>>> for i in a:
. print(i)
.
1
None
2
None
3
None
Copy the code
yield from
The following example demonstrates the use of yield from
def myfun(total):
for i in range(total):
yield i
yield from ['a'.'b'.'c']
Copy the code
The next call results in the following
>>> m = myfun(3)
>>> next(m)
0
>>> next(m)
1
>>> next(m)
2
>>> next(m)
'a'
>>> next(m)
'b'
>>> next(m)
'c'
>>> next(m)
Traceback (most recent call last):
File "<stdin>", line 1.in <module>
StopIteration
Copy the code
The function above is equal to
def myfun(total):
for i in range(total):
yield i
for i in ['a'.'b'.'c'] :
yield i
Copy the code
Let’s also do a little exercise to implement cycle in the iterTools module
>>> from itertools import cycle
>>> a = cycle('abc')
>>> next(a)
'a'
>>> next(a)
'b'
>>> next(a)
'c'
>>> next(a)
'a'
>>> next(a)
'b'
>>> next(a)
'c'
>>> next(a)
'a'
Copy the code
To achieve the following
def cycle(p):
yield from p
yield from cycle(p)
Copy the code
The send and yield values are assigned
Let’s start with send, and just to be clear, next is the same thing as Send (None), so let’s look at the simplest example
>>> def myfun(total):
. for i in range(total):
. yield i
.
>>> a = myfun(3)
>>> a.send(None)
0
>>> a.send(None)
1
>>> a.send(None)
2
>>> a.send(None)
Traceback (most recent call last):
File "<stdin>", line 1.in <module>
StopIteration
Copy the code
What if the send() argument is not None? Send () means you send something to this generator, and if you send something, you have to have a variable and then, yield assignment. Let’s do an example
def myfun(total):
for i in range(total):
r = yield i
print(r)
Copy the code
Run the following
>>> a = myfun(3)
>>> a.send(1)
Traceback (most recent call last):
File "<stdin>", line 1.in <module>
TypeError: can't send non-None value to a just-started generator
>>> a.send(None)
0
>>> a.send(1)
1
1
>>> a.send(1)
1
2
>>> a.send(1)
1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Copy the code
The above run results show
1. The first send parameter must be None to initialize the generator
- The operation details of the whole program are very important, detailed disassembly is as follows
- The first step
a.send(None)
, is to execute the first loop, run toyield i
. Note: Not runningr = yield
This assignment step, this is the first time that you can only pass inNone
The reason why - The second step
a.send(1)
First,r = yield
The assignment will besend
The 1 that goes in is assigned tor
; thenprint(r)
That’s the first one printed out; Then it enters the second loop and runs toyield i
There is no assignmentyield
The result 1 of is printed, the second 1 - The last time
a.send(1)
On the firstr
Assignment, againprint(r)
; And then you’re out of the loop, and what can you doyield
Throws an exception
With a mechanism like SEND, we can switch back and forth between functions, which is the basis of coroutines.
Teacher Liao Xuefeng’s website uses this feature to complete an example similar to the producer-consumer model. Readers can see if they can understand this example according to the knowledge above
def consumer(a):
r = ' '
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s... ' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s... ' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
Copy the code
The result is as follows
[PRODUCER] Producing 1..
[CONSUMER] Consuming 1..
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2..
[CONSUMER] Consuming 2..
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3..
[CONSUMER] Consuming 3..
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4..
[CONSUMER] Consuming 4..
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5..
[CONSUMER] Consuming 5..
[PRODUCER] Consumer return: 200 OK
Copy the code
The above example looks complicated and elegant, but if you look at the implementation of the above, it is actually a detour. The following code can achieve the same function as the above, so the reader can take a look
def consumer(n):
if not n:
return
print('[CONSUMER] Consuming %s... ' % n)
return '200 OK'
def produce(a):
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s... ' % n)
r = consumer(n)
print('[PRODUCER] Consumer return: %s' % r)
produce()
Copy the code
return yield
Sometimes you’ll see return yield, but return just terminates the function, so let’s look at the following function
def myfun(total):
yield from range(total)
Copy the code
>>> a = myfun(4)
>>> a
<generator object myfun at 0x000001B61CCB9CA8>
>>> for i in a:
. print(i)
.
0
1
2
3
Copy the code
So a is a generator. Same thing if you use return
# no parentheses
>>> def myfun(total):
. return yield from range(total)
File "<stdin>", line 2
return yield from range(total)
^
SyntaxError: invalid syntax
>>> def myfun(total):
. return (yield from range(total))
.
>>> a = myfun(4)
>>> for i in a:
. print(i)
.
0
1
2
3
Copy the code
But the next two are different
def myfun1(total):
return (yield from range(total))
yield 1 # this 1 is not executed after return
def myfun2(total):
yield from range(total)
yield 1
Copy the code
See the result below
>>> a = myfun1(3)
>>> for i in a:
. print(i)
.
0
1
2
>>> a = myfun2(3)
>>> for i in a:
. print(i)
.
0
1
2
1
Copy the code
Welcome to my zhihu column
Column home: Programming in Python
Table of contents: table of contents
Version description: Software and package version description