学 Python 怎能不知 yield?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学 Python 怎能不知 yield?相关的知识,希望对你有一定的参考价值。

参考技术A


理解yield 的 generator 概念,首先以一个常见的编程题目来展示 yield 的概念。


斐波那契(Fibonacci)数列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到。用计算机程序输出斐波那契数列的前 N 个数是一个非常简单的问题,有些 Python 基础的小伙伴都可以轻易写出如下函数:

第 1 版本:简单输出斐波那契数列前 N 个数

执行以上代码,我们可以得到如下输出:

输出结果是没有问题的,但是版本 1 中的写法是直接在 createNum 函数中用 print 打印数字会导致该函数可复用性较差,因为 createNum 函数返回 None,其他函数无法获得该函数生成的数列。

要提高 createNum 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。以下是 createNum 函数改写后的第二个版本:

第 2 版本:输出斐波那契数列前 N 个数

该版本中 createNum 函数返回的 List的结果如下:

改写后的 createNum 函数通过返回 List 能满足复用性的要求,但是与此同时也会存在一个明显的问题是:该函数在运行中占用的内存会随着参数 count 的增大而增大,如果要控制内存占用,最好不要用 List 来保存中间结果,而是通过 iterable 对象来迭代。在每次迭代中返回下一个数值,如此:内存空间占用很小。因为是直接返回一个 iterable 对象。

第 3 版本:使用 yield 输出斐波那契数列前 N 个数

也可以手动调用 createNum(5) 的 next() 方法(因为 createNum(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 createNum 的执行流程:

第 4 版本:执行流程

运行以上代码,结果输出如下:

由输出结果可发现在执行第 6 个 print(next(num)) 时抛出一个 StopIteration 的异常,是因为在第 5 个 print(next(num)) 执行完时函数已经结束,再执行第 6 个print(next(num))时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。


简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 createNum(5) 不会执行 createNum 函数,而是返回一个 iterable 对象!

在 for 循环执行时,每次循环都会执行 createNum 函数内部的代码,执行到 yield b 时,createNum 函数就会返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。



一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。


yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。

python学习之生成器yield

python学习之生成器yield

**yield的作用是使函数生成一个结果序列而不是一个值,任何使用yield的函数都称为生成器,调用生成器会创建一个对象,该对象通过连续调用next()或者__next__()方法生成结果序列**

一般情况

>>> def count(n,m):
>>>     print(‘这是一个循环外部测试‘)
>>>     while n>0:
>>>         print(‘这是一个循环内部测试‘)
>>>         yield n,m
>>>         n-=1


>>> c=count(5,2)
>>> c.__next__()
这是一个循环外部测试
这是一个循环内部测试
5,2

首先我定义了生成器,利用了yield关键词,但是注意,我没有像其它列子一样使用单个参数,yield的后面是两个参数,这里就是为了提醒,yield后面可以跟多个参数,他的后面参数表示要传给yield的这个关键词的参数,甚至可以用表达式,但是一定要用括号括起来,比如yield (n+3),这样的参数也可以

  1. 当初次调用count的时候,该函数什么也不做,因为他知道这是个yield的函数,所以也不会打印“这是一个外部循环测试"这段文字,但是,当调用__next__()之后,函数开始执行,执行到yield关键词,然后停止执行,函数的返回值就是要传给yield关键词的参数(其实用yield关键词相当于古时候的驿站,参数过来歇歇脚,然后继续上路,每遇到一次yield就要歇脚一次,调用一次__next__(),相当于开始赶路一次),就是如上图返回的5,2
  2. 当再次执行时,该函数从yield处开始执行,执行减一操作,然后继续循环,遇到yield停止
>>> c.__next__()
这是一个循环内部测试
4,2
>>> c.__next__()
这是一个内部循环测试
3,2
>>> c.__next__()
这是一个内部循环测试
2,2

下面一个python参考手册上的一个列子

利用pyhton实现linux上的tail -f |grep python这个命令

import time
def tail(f):
    f.seek(0,2) #移动到EOF
    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


wwwlog=tail(open(‘access-log‘))
pylines=grep(wwwlog,‘python‘)
for line in pylines:
    print(line)

以上是关于学 Python 怎能不知 yield?的主要内容,如果未能解决你的问题,请参考以下文章

python学习之生成器yield

Python学习之十yield之send方法

Python学习之yield表达式三元表达式与生成式

python学习之-生成器

python学习之函数返回值

Python3 中 Yield 理解与使用