python与协程(1/3):生成器与协程

本文主要是从 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

  1. 协程(coroutine)

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开始生成。

简单的说,生成器为迭代器生成数据

而协程是数据的消费者

你不应该把它们弄混,协程和迭代器不相关。

你可能感兴趣的:(python)