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

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

而协程是数据的消费者

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值