本文主要是从 A Curious Course on Coroutines and Concurrency 作笔记而来。
1,生成器:
生成器是一个包含yield表达式的函数,这个函数与普通函数不同
--它返回一个生成器
--首次执行时并不真正运行,只是返回一个生成器
--首次对这个生成器调用内置的next函数(python2.6+ 使用 generator. next()),会让函数运行一次到yield,yield生成数据并挂起这个函数
--以后的每次next调用,都会从上一次挂起处的yield表达式继续运行,进入下一次生成
--当生成器返回时,会抛出StopIteration异常,如果在迭代中,会使迭代停止
>>> def countdown(n):
print('count down from {0}'.format(n))
while n > 0 :
yield n
n -= 1
... ... ... ... ...
>>> x=countdown(5)
print('x is set')
for i in x:
print(i)>>> x is set
>>> ...
...
count down from 5
5
4
3
2
1
>>> x
<generator object countdown at 0x100557b90>
>>> x=countdown(5)
>>> x
<generator object countdown at 0x100557c30>
>>> next(x)
count down from 5
5
>>> next(x)
4
>>> next(x)
3
>>> next(x)
2
>>> next(x)
1
>>> next(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
另外,演示里给出了一个taill -f 的python实现
(tail -f 是unix里的工具 -f 的作用是监控制某个文件,
如果有新的数据追加到此文件,就输出来,这个功能在看日志时一般会用到)
>>> import time
def follow(file):
file.seek(0,2)
while True:
line = file.readline()
if not line:
time.sleep(0.1)
continue
yield line
logfile=open('a')
for line in follow(logfile):
print("{0}".format(line),end="")>>> ... ... ... ... ... ... ... ... >>> >>> >>> ...
...
b
last
^CTraceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in follow
KeyboardInterrupt
我们在另外的shell里
echo b >> a
echo last >> a
就可以让tail -f 的程序输出。
生成式组成管道流水线
生成器作为管道: 将上个生成器的输出作为下一个生成器的输出,就构成了一个管道
(管道是unix 下的一种进程间通讯工具,程序可以将自己的输出数据作为后一个程序的输入数据
比如:ls -l命令输出的文件列表:
lymatoMacBook-Pro:co ly$ ls -l
total 56
-rw-r--r-- 1 ly staff 43 Feb 2 15:27 a
-rw-r--r-- 1 ly staff 0 Feb 1 23:21 a.log
-rw-r--r-- 1 ly staff 172 Feb 2 10:01 countdown.py
-rwxr-xr-x 1 ly staff 9056 Feb 2 13:52 d
-rw-r--r-- 1 ly staff 606 Feb 2 14:06 d.c
-rw-r--r-- 1 ly staff 271 Feb 1 23:46 tailf.py
我们使用管道对这个数据按文件大小排序:
lymatoMacBook-Pro:co ly$ ls -l | sed 1d | sort -nk5
-rw-r--r-- 1 ly staff 0 Feb 1 23:21 a.log
-rw-r--r-- 1 ly staff 43 Feb 2 15:27 a
-rw-r--r-- 1 ly staff 172 Feb 2 10:01 countdown.py
-rw-r--r-- 1 ly staff 271 Feb 1 23:46 tailf.py
-rw-r--r-- 1 ly staff 606 Feb 2 14:06 d.c
-rwxr-xr-x 1 ly staff 9056 Feb 2 13:52 d
ls 的输出经由管道,由sed 和sort 过滤与重组以后,输出)
我们可以通过将yield 表达式作为下一个表达式的输出构成一个流水线:
import time
def follow(file):
file.seek(0,2)
while True:
line = file.readline()
if not line:
time.sleep(0.1)
continue
yield line
logfile=open('a')
loglines=follow(logfile)
def search(pattern, lines):
for line in lines:
if pattern in line:
yield line
pylines = search('python', loglines)
for pyline in pylines:
print("{0}".format(pyline),end="")
follow的输入被search 当作输入流过滤,就有了 tailf -f a | grep python的效果
lymatoMacBook-Pro:co ly$ python3 pipeline.py
python
we mention python
we mention python in line again
yield可以作为(右值),从而变成协程
def search(pattern):
print ('looking for {0}'.format(pattern))
while True:
line = (yield)
if pattern in line:
print(line)
p = search('python')
>>> ... ... ... ... ... ... ... ... >>> >>> >>> >>> >>> ... ... ... ... ... ... >>> >>> >>> >>>
>>> p
<generator object search at 0x100557be0>
>>> next(p)
looking for python
>>> p.send('python')
python
>>> p.send('no ')
>>>
yield作为右值表达式以后成为协程,
在next调用以后,协程search 运行到yield表达式并挂起,等待输入
除了生成数据,协程还可以接受数据(使用generator.send)
协程只对 三个方法响应:全局next, 函数方法:send,close
协程需要使用一次next方法或send(None)启动,这个调用会使协程运行到第一次yield处。
既然每个协程都需要运行一次next,我们可以使用修饰器对函数进行修饰
(被修饰器包装过的函数,运行时实际是运行的修饰器。)
def coroutine(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
generator = function(*args, **kwargs)
next(generator) #prime the generator
return generator
return wrapper
@coroutine
def search(pattern):
print (‘looking for {0}’.format(pattern))
while True:
line = (yield)
if pattern in line:
print(line)
p = search(‘python’)
>>> >>> … … … … … … … >>> … … … … … … … >>> looking for python
>>> >>> >>>
>>>
>>> p
<generator object search at 0x100557b90>
>>> p.send(‘python here’)
python here
>>> p.close()
>>> p.send(‘p’)
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
StopIteration
被包装以后的函数,在首次调用以后,修饰器会调用一次next
使用generator.close()以后,发送给已关闭的生成器的数据会引发异常
垃圾收集器也会调用close()方法
close会引起GeneratorExit异常,而我们也可以捕捉这个异常
>>> import functools
def coroutine(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
generator = function(*args, **kwargs)
next(generator) #prime the generator
return generator
return wrapper
@coroutine
def search(pattern):
print (‘looking for {0}’.format(pattern))
try:
while True:
line = (yield)
if pattern in line:
print(line)
except GeneratorExit:
print(‘generator closed’)
p = search(‘python’)
p.send(‘python 3’)
p.close()
>>> >>> … … … … … … … >>> … … … … … … … … … … >>> looking for python
>>> python 3
>>> generator closed
此异常不可以忽略,唯一合法的处理是清理并退出
异常可以在yied 表达式中抛出。
>>> p.throw(RuntimeError, 'except here')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in search
RuntimeError: except here
上边所示的异常就是在yield中抛出的。
在yield表达式出抛出的异常可以和普通的异常一样处理
生成器与协程的对比:
尽管生成器与协程极 其相似,但二者还是两个根本不同的概念:
生成器生成数据
而协程用来消耗数据
还是比较容易区分两者的,对协程有意义的方法有时被描述成翻转产生迭代的生成器用法
(比如重置生成器的值)这挺讨厌人的。
一个讨厌的例子:
>>> def countdown(n):
print("Counting down from {0}".format( n))
while n >= 0:
newvalue = (yield n)
# If a new value got sent in, reset n with it
if newvalue is not None:
n = newvalue
else:
n -= 1
c = countdown(5)
for n in c:
print(n)
if n == 5:
c.send(3)
... ... ... ... ... ... ... ... ... >>> >>> >>> ... ... ... ... Counting down from 5
5
3
2
1
0
>>>
在生成器生成5..0的序列时,调用c.send(3),会扭转生成器从3开始生成。
简单的说,生成器为迭代器生成数据
而协程是数据的消费者
你不应该把它们弄混,协程和迭代器不相关。