设为首页 收藏本站
查看: 701|回复: 0

[经验分享] python与协程(1/3):生成器与协程

[复制链接]

尚未签到

发表于 2017-5-4 09:55:35 | 显示全部楼层 |阅读模式
本文主要是从 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





  • 协程(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开始生成。



简单的说,生成器为迭代器生成数据
而协程是数据的消费者
你不应该把它们弄混,协程和迭代器不相关。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-372842-1-1.html 上篇帖子: python 提高效率的几个小技巧 下篇帖子: 软件开发流程安排(以Python语言为例说明)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表