In the last article , We introduced Python Medium yield Keywords and generator functions that depend on their implementation . python Iterators and generators in
The generator function is very close to the coroutine in form , In this article, we will introduce the cooperative process in detail .
Coroutines are also known as micro threads , Although there is only one thread in the whole execution process , But the execution of a method can be suspended 、 Give up CPU Give me another way , Wait until the appropriate time to return to the original method to continue execution , But there is no calling relationship between the two methods , They are similar to system interrupts or multithreading . thus , We can see the following advantages of cooperative process :
>>> def simple_coroutine():
... print('-> coroutine started')
... x = yield
... print('-> coroutine received:', x)
...
>>> my_coro = simple_coroutine()
>>> my_coro
<generator object simple_coroutine at 0x100c2be10>
>>> next(my_coro)
-> coroutine started
>>> my_coro.send(42)
-> coroutine received: 42
Traceback (most recent call last):
...
StopIteration
You can see , In the example above ,yield It is no longer the familiar appearance on the left of the formula , Instead, it becomes the right value of the variable assignment , in fact , here yield Values can also appear on the right 、 Variable or expression . When the program is executed to yield When the expression , The collaboration process is suspended , At the same time return to yield Value on the right ( If any ) Execute on this collaboration send The operation is actually to send The parameters of the method are passed to yield The lvalue of an expression , Then the program continues to run .
There are four states in a collaboration process :
By using inspect.getgeneratorstate Function can return one of the above four status strings . Only when a coroutine is in GEN_SUSPENDED State can be called send Method , Otherwise, an exception will be thrown :
>>> my_coro = simple_coroutine()
>>> my_coro.send(1729)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
>>> def simple_coro2(a):
... print('-> Started: a =', a)
... b = yield a
... print('-> Received: b =', b)
... c = yield a + b
... print('-> Received: c =', c)
...
>>> my_coro2 = simple_coro2(14)
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(my_coro2)
'GEN_CREATED'
>>> next(my_coro2)
-> Started: a = 14
14
>>> getgeneratorstate(my_coro2)
'GEN_SUSPENDED'
>>> my_coro2.send(28)
-> Received: b = 28
42
>>> my_coro2.send(99)
-> Received: c = 99
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> getgeneratorstate(my_coro2)
'GEN_CLOSED'
The following figure shows the execution of the above code :
So you need to call... First next Method , Let the coordination process run to the first yield expression , This process is called “ Pre excitation ”(prime) All co processes must be pre excited and then used , This time, next Calls always seem superfluous , Without him, he would make a mistake , So we can add a decorator to our cooperation , To automatically complete the pre excitation function after the collaboration is created .
from functools import wraps
def coroutine(func):
@wraps(func)
def primer(*args,**kwargs):
gen = func(*args,**kwargs)
next(gen)
return gen
return primer
About decorators , You can refer to : python Decorator and its principle in
There are several situations that will make the process enter GEN_CLOSED state :
About Python Garbage collection mechanism , Reference resources : python Memory management and garbage collection
from collections import namedtuple
Result = namedtuple('Result', 'count average')
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break
total += term
count += 1
average = total/count
You can see , In the above example , A coroutine is an infinite loop , As long as the caller keeps sending values to the coroutine , He will keep adding up 、 Calculate the moving average , Until coordinated close Method is called or the collaboration object is garbage collected .
In the following example , We added a return statement at the end of the above code to calculate the moving average .
from collections import namedtuple
Result = namedtuple('Result', 'count average')
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break
total += term
count += 1
average = total/count
return Result(count, average)
>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(10)
>>> coro_avg.send(30)
>>> coro_avg.send(6.5)
>>> coro_avg.send(None)
Traceback (most recent call last):
...
StopIteration: Result(count=3, average=15.5)
You can see , In the end, send to the coordination process None This leads to the withdrawal of the coordination process , Throw the StopIteration This return value is carried in , adopt StopIteration Of value Field we can take the value :
yield from Statement can simplify... In generator functions yield expression , This has been introduced in our previous article :
>>> def gen():
... for c in 'AB':
... yield c
... for i in range(1, 3):
... yield i
...
>>> list(gen())
['A', 'B', 1, 2]
It can be rewritten as :
>>> def gen():
... yield from 'AB'
... yield from range(1, 3)
...
>>> list(gen())
['A', 'B', 1, 2]
contain yield from The function of the statement is called a delegate generator , He opened a two-way passage , Connect the outermost caller to the innermost child generator , Enables both to send and output values directly , You can also pass in exceptions directly , In the middle of the process in order to add any intermediate processing code . yield from Statement will wait for the child generator to terminate and throw StopIteration abnormal , And the sub generator passes return The value returned by the statement will become yield from Statement .