码迷,mamicode.com
首页 > 其他好文 > 详细

生成器-generator

时间:2017-02-05 22:55:05      阅读:245      评论:0      收藏:0      [点我收藏+]

标签:循环   函数调用   start   分割   class   斐波纳契数列   不同   targe   leo   

        您可能听说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ?

我们先抛开 generator,以一个常见的编程题目来展示 yield 的概念。

        如何产生斐波拉契数列?

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)

        许多初学者都可以写出下面的函数:

方法一:

1 # 生成斐波那契数列前n个元素
2 def fab(n):
3     i = 0
4     a, b = 0, 1
5     while i < n:
6         print(a)
7         a, b = b, a + b
8         i += 1

执行 fab(6) 得出前6个元素:

0
1
1
2
3
5

结果没有问题,但函数的返回值为None,别的函数无法获取结果,复用性很差,

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

方法二:

 1 # 生成斐波那契数列前n个元素,以list返回
 2 def fab(n):
 3     i = 0
 4     a, b = 0, 1
 5     list_fab = []
 6     while i < n:
 7         # print(a)
 8         list_fab.append(a)
 9         a, b = b, a + b
10         i += 1
11 
12     return list_fab

可以使用如下方式打印出 fab 函数返回的 List:

1 for item in fab(6):
2     print(item)

改写后的 fab 函数通过返回 List 能满足复用性的要求,但是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 n的增大而增大,如果要控制内存占用,最好不要用 List,来保存中间结果,而该通过 iterable 对象来迭代,这时yield就派上用场了。

第三个版本的 fab 和第一版相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。

方法三:

1 # 使用 yield替代print
2 def fab(n):
3     i = 0
4     a, b = 0, 1
5     while i < n:
6         # print(a)
7         yield a
8         a, b = b, a + b
9         i += 1

简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(6) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield a 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield a 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

for item in fab(6):
    print(item)

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

1 f = fab(6)
2 print(next(f))
3 print(next(f))
4 print(next(f))
5 print(next(f))
6 print(next(f))
7 print(next(f))
8 print(next(f))

当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

我们可以得出以下结论:

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

 为了更好的理解上面一段话,我们看下面的例子:

1 def test_yield():
2     print("test start")
3     yield 1
4     yield 2
5     yield 3
6     print("test stop")
7 
8 result = test_yield()
9 print(result, type(result))

在函数调用 result = test_yield() 时, print("test start") 这一句代码并不会执行,程序不输出test start的打印

 print(result, type(result)) 的执行结果是: <generator object test_yield at 0x02D35D00> <class generator> 

当在上面代码后再执行 print(next(result)) 时,

程序输出:

1 test start
2 1

我们再用for循环输出result:

1 for i in result:
2     print(i)

这是前后两句print都将打印出来:

1 test start
2 1
3 2
4 3
5 test stop

 

#Note:本文参考另外一篇博文

原文链接:http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

 

生成器-generator

标签:循环   函数调用   start   分割   class   斐波纳契数列   不同   targe   leo   

原文地址:http://www.cnblogs.com/z-joshua/p/6368606.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!