python yield生成器

来源:互联网 发布:上网淘宝 编辑:程序博客网 时间:2024/05/18 01:37

下面是学习生成器的比较好的博客,本文的大部分内容也是借鉴的这些博客:
python 模拟 linux shell 下的 tail -f 动态打印日志
python yield生成器
提高你的Python: 解释‘yield’和‘Generators(生成器)’
python关键字yield的解释
Python yield 使用浅析
Python Generators(生成器)——yield关键字

用yield生成器模拟Linux中命令:tail -f | grep python 用于查找监控日志文件中出现有python字样的行。
下面先是网上的代码:

    import time      def tail(f):          f.seek(0,2)#移动到文件EOF,参考:[seek](http://docs.python.org/2/library/stdtypes.html?highlight=file#file.seek)          while True:              line = f.readline()  #读取文件中新的文本行              if not line:                  time.sleep(0.1)                  continue              yield line      def grep(lines,searchtext):          for line in lines:              if searchtext in line:                  yield line     flog = tail(open('warn.log'))      pylines = grep(flog,'python')      for line in pylines:          print line,   

但是这样反向读取文件在3.4.3好像读不出来,自己又换了一种方式

import timedef tail(f):    lines=f.readlines()    reversed(lines)    for line in lines:        if not line:            time.sleep(0.1)            continue        yield linedef grep(lines,searchtext):    for line in lines:        if searchtext in line:            yield lineflog = tail(open('in.txt'))pylines = grep(flog,'2')for line in pylines:    print(line,end=' ')

yield中return的作用:
作为生成器,因为每次迭代就会返回一个值,所以不能显示的在生成器函数中return 某个值,包括None值也不行,否则会抛出“SyntaxError”的异常,但是在函数中可以出现单独的return,表示结束该语句。
通过固定长度的缓冲区不断读文件,防止一次性读取出现内存溢出的例子:

def read_file(path):    size=1024    with open(path,'r') as f:        while True:            block=f.read(size)            if block:                yield block            else:                return

=======================================================

协程与子例程

我们调用一个普通的Python函数时,一般是从函数的第一行代码开始执行,结束于return语句、异常或者函数结束(可以看作隐式的返回None)。一旦函数将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。

对于在计算机编程中所讨论的函数,这是很标准的流程。这样的函数只能返回一个值,不过,有时可以创建能产生一个序列的函数还是有帮助的。要做到这一点,这种函数需要能够“保存自己的工作”。

我说过,能够“产生一个序列”是因为我们的函数并没有像通常意义那样返回。return隐含的意思是函数正将执行代码的控制权返回给函数被调用的地方。而”yield”的隐含意思是控制权的转移是临时和自愿的,我们的函数将来还会收回控制权。

在Python中,拥有这种能力的“函数”被称为生成器,它非常的有用。生成器(以及yield语句)最初的引入是为了让程序员可以更简单的编写用来产生值的序列的代码。 以前,要实现类似随机数生成器的东西,需要实现一个类或者一个模块,在生成数据的同时保持对每次调用之间状态的跟踪。引入生成器之后,这变得非常简单。

为了更好的理解生成器所解决的问题,让我们来看一个例子。在了解这个例子的过程中,请始终记住我们需要解决的问题:生成值的序列。

注意:在Python之外,最简单的生成器应该是被称为协程(coroutines)的东西。在本文中,我将使用这个术语。请记住,在Python的概念中,这里提到的协程就是生成器。Python正式的术语是生成器;协程只是便于讨论,在语言层面并没有正式定义。

例子:有趣的素数

假设你的老板让你写一个函数,输入参数是一个int的list,返回一个可以迭代的包含素数1 的结果。

记住,迭代器(Iterable) 只是对象每次返回特定成员的一种能力。

你肯定认为”这很简单”,然后很快写出下面的代码:

import mathdef is_prime(x):    if x<=1:return False    for cur in range(2,int(math.sqrt(x)+1)):        if x%cur==0:            return False    return Truedef get_prime1(input_list):    res=[]    for element in input_list:        if is_prime(element):            res.append(element)    return resdef get_prime2(input_list):    return [element for element in input_list if is_prime(element)]a=[1,2,3,4,5,6,7,8,9,11]b=get_prime1(a)print(b)b=get_prime2(a)print(b)

处理无限序列

噢,真是如此吗?过了几天,老板过来告诉我们她遇到了一些小问题:她打算把我们的get_primes函数用于一个很大的包含数字的list。实际上,这个list非常大,仅仅是创建这个list就会用完系统的所有内存。为此,她希望能够在调用get_primes函数时带上一个start参数,返回所有大于这个参数的素数(也许她要解决 Project Euler problem 10)。

我们来看看这个新需求,很明显只是简单的修改get_primes是不可能的。 自然,我们不可能返回包含从start到无穷的所有的素数的列表 (虽然有很多有用的应用程序可以用来操作无限序列)。看上去用普通函数处理这个问题的可能性比较渺茫。 处理无限序列

在我们放弃之前,让我们确定一下最核心的障碍,是什么阻止我们编写满足老板新需求的函数。通过思考,我们得到这样的结论:函数只有一次返回结果的机会,因而必须一次返回所有的结果。得出这样的结论似乎毫无意义;“函数不就是这样工作的么”,通常我们都这么认为的。可是,不学不成,不问不知,“如果它们并非如此呢?”

想象一下,如果get_primes可以只是简单返回下一个值,而不是一次返回全部的值,我们能做什么?我们就不再需要创建列表。没有列表,就没有内存的问题。由于老板告诉我们的是,她只需要遍历结果,她不会知道我们实现上的区别。

走进生成器

这类问题极其常见以至于Python专门加入了一个结构来解决它:生成器。一个生成器会“生成”值。创建一个生成器几乎和生成器函数的原理一样简单。

一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用yield关键字而不是return。如果一个def的主体包含yield,这个函数会自动变成一个生成器(即使它包含一个return)。除了以上内容,创建一个生成器没有什么多余步骤了。

生成器函数返回生成器的迭代器。这可能是你最后一次见到“生成器的迭代器”这个术语了, 因为它们通常就被称作“生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法(method),其中一个就是next()。如同迭代器一样,我们可以使用next()函数来获取下一个值。

为了从生成器获取下一个值,我们使用next()函数,就像对付迭代器一样。

(next()会操心如何调用生成器的next()方法)。既然生成器是一个迭代器,它可以被用在for循环中。

每当生成器被调用的时候,它会返回一个值给调用者。在生成器内部使用yield来完成这个动作(例如yield 7)。为了记住yield到底干了什么,最简单的方法是把它当作专门给生成器函数用的特殊的return(加上点小魔法)。**

yield就是专门给生成器用的return(加上点小魔法)。

魔法?

那么神奇的部分在哪里?我很高兴你问了这个问题!当一个生成器函数调用yield,生成器函数的“状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用next()。一旦next()再次被调用,生成器函数会从它上次离开的地方开始。如果永远不调用next(),yield保存的状态就被无视了。

我们来重写get_primes()函数,这次我们把它写作一个生成器。注意我们不再需要magical_infinite_range函数了。使用一个简单的while循环,我们创造了自己的无穷串列。

def get_primes(number):    while True:        if is_prime(number):            yield number        number += 1

如果生成器函数调用了return,或者执行到函数的末尾,会出现一个StopIteration异常。 这会通知next()的调用者这个生成器没有下一个值了(这就是普通迭代器的行为)。这也是这个while循环在我们的get_primes()函数出现的原因。如果没有这个while,当我们第二次调用next()的时候,生成器函数会执行到函数末尾,触发StopIteration异常。一旦生成器的值用完了,再调用next()就会出现错误,所以你只能将每个生成器的使用一次。
yield会将值传给next()的调用方,同时还会保存生成器函数的“状态

更给力点

在PEP 342中加入了将值传给生成器的支持。PEP 342加入了新的特性,能让生成器在单一语句中实现,生成一个值(像从前一样),接受一个值,或同时生成一个值并接受一个值。

我们用前面那个关于素数的函数来展示如何将一个值传给生成器。这一次,我们不再简单地生成比某个数大的素数,而是找出比某个数的等比级数大的最小素数(例如10, 我们要生成比10,100,1000,10000 … 大的最小素数)。我们从get_primes开始:

def print_successive_primes(iterations, base=10):    # 像普通函数一样,生成器函数可以接受一个参数    prime_generator = get_primes(base)    # 这里以后要加上点什么    for power in range(iterations):        # 这里以后要加上点什么def get_primes(number):    while True:        if is_prime(number):        # 这里怎么写?

get_primes的后几行需要着重解释。yield关键字返回number的值,而像 other = yield foo 这样的语句的意思是,”返回foo的值,这个值返回给调用者的同时,将other的值也设置为那个值”。你可以通过send方法来将一个值”发送“给生成器。 get_primes的后几行需要着重解释。yield关键字返回number的值,而像 other = yield foo 这样的语句的意思是,”返回foo的值,这个值返回给调用者的同时,将other的值也设置为那个值”。你可以通过send方法来将一个值”发送“给生成器。

def get_primes(number):    while True:        if is_prime(number):            number = yield number        number += 1

通过这种方式,我们可以在每次执行yield的时候为number设置不同的值。现在我们可以补齐print_successive_primes中缺少的那部分代码:

def print_successive_primes(iterations, base=10):    prime_generator = get_primes(base)    prime_generator.send(None)    for power in range(iterations):        print(prime_generator.send(base ** power))

这里有两点需要注意:首先,我们打印的是generator.send的结果,这是没问题的,因为send在发送数据给生成器的同时还返回生成器通过yield生成的值(就如同生成器中yield语句做的那样)。

第二点,看一下prime_generator.send(None)这一行,当你用send来“启动”一个生成器时(就是从生成器函数的第一行代码执行到第一个yield语句的位置),你必须发送None。这不难理解,根据刚才的描述,生成器还没有走到第一个yield语句,如果我们发生一个真实的值,这时是没有人去“接收”它的。一旦生成器启动了,我们就可以像上面那样发送数据了。

0 0
原创粉丝点击