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

  1. The operation details of the whole program are very important, detailed disassembly is as follows
  • The first stepa.send(None), is to execute the first loop, run toyield i. Note: Not runningr = yieldThis assignment step, this is the first time that you can only pass inNoneThe reason why
  • The second stepa.send(1)First,r = yieldThe assignment will besendThe 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 iThere is no assignmentyieldThe result 1 of is printed, the second 1
  • The last timea.send(1)On the firstrAssignment, againprint(r); And then you’re out of the loop, and what can you doyieldThrows 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