1 生成器定义

在Python中,一边循环一边计算的机制,称之为生成器(generator)。

生成器是一个迭代器。

含有yield语句的函数是生成器函数,该函数被调用时返回一个生成器对象(yield译为产生或生成)。

生成器拥有next方法并且行为与迭代器完全相同,这意味着生成器也可以用于Python的for循环中。

另外,对于生成器的特殊语法支持使得编写一个生成器比自定义一个常规的迭代器要简单不少,所以生成器也是最常用到的特性之一。

简而言之:

yield用于def函数中,目的是将此函数作为生成器函数使用

yield用来生成数据,供迭代器的next(Iterator) 函数使用

2 生成器分类

生成器有两种,一种生成器函数,一种是生成器表达式(列表推导式

3 使用生成器函数定义生成器

>>> def gen():
...     print("第一次输出")
...     yield 1
...     print("第二次输出")
...     yield 2
...     print("第三次输出")
...     yield 3
...
>>> gen()
<generator object gen at 0x7f23f7f9a780>
 

定义了一个函数类型gen(),但该函数内使用了关键字yield,使得这个函数成为了生成器函数。

1) 调用生成器函数将返回一个生成器

>>> gen1 = gen()
>>> gen1
<generator object gen at 0x7f23f7f9a830>

2) 第一次调用生成器的next()方法时,生成器才开始执行生成器函数(而不是构建生成器时),直到遇到yield时暂停执行(挂起),并且yield的参数将作为此次next方法的返回值;

>>> g1 = gen()
>>> next(gen())
第一次输出
1
>>> next(gen())
第一次输出
1

>>> next(g1)
第一次输出
1
>>> next(g1)
第二次输出
2

>>> g2 = g1
>>> next(g2)
第三次输出
3

注意:

(1)gen()是一个生成器函数,每一次调用,它都会重新执行,同时该函数运行到yeild()时会暂停执行,所以两次执行(函数调用)均输出相同的值。

(2)而g1 、g2是调用函数gen()返回的生成器,为了说明问题,举例如下:

说明g1 和 g2 绑定的一个生成器对象,所以在执行next()时会顺次执行。

3)之后每次调用生成器的next方法,生成器将从上次暂停执行的位置恢复执行生成器对象,直到再次遇到yield时暂停,并且同样的,yield的参数将作为next方法的返回值;

4)如果当调用next方法时生成器函数结束(遇到空的return语句或是到达函数体末尾),则这次next方法的调用将抛出StopIteration异常(即for循环的终止条件);

5)生成器函数在每次暂停执行时,函数体内的所有变量都将被封存(freeze)在生成器中,并将在恢复执行时还原,并且类似于闭包,即使是同一个生成器函数返回的生成器,封存的变量也是互相独立的。

我们的小例子中并没有用到变量,所以这里另外定义一个生成器来展示这个特点:

>>> def fibonacci():
...     a = b = 1
...     yield a
...     yield b
...     while True:
...             a, b = b, a+b
...             yield b
...
>>> for num in fibonacci():
...     if num > 100:
...             break
...     print(num)
...
1
1
2
3
5
8
13
21
34
55
89

或者

>>> def fibonacci():
...     a = b = 1
...     yield a
...     yield b
...     while True:
...             a, b = b, a+b
...             yield b
...
>>> next(fibonacci())
1
>>> next(fibonacci())
1
>>> next(fibonacci())
1
>>> f = fibonacci()
>>> next(f)
1
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3
>>> next(f)
5
>>> next(f)
8
>>> next(f)
13
>>> next(f)
21
>>> next(f)
34
>>> next(f)
55
>>> next(f)
89
>>> next(f)
144
>>> next(f)
233
>>> next(f)
377
>>> next(f)
610
>>> next(f)
987

看到while True可别太吃惊,因为生成器可以挂起,所以是延迟计算的,无限循环并没有关系。这个例子中我们定义了一个生成器用于获取斐波那契数列。

4 一种是生成器表达式

生成器表达式(采用的是小括号“()”形式)类似于列表推导式(采用的是小括号“[ ]”形式),生成器的返回值是按每次next()调用产生一个对象,需要的存储空间较小,而列表推导式则是一次性构建一个列表库,需要的存储空间较大。

#列表表达式,生成一个元素库
>>> a = [x for x in range(1,10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9]

#生成器
>>> b = (x for x in range(1,10))
>>> b
<generator object <genexpr> at 0x7fb2bf6f24c0>

生成器表达式与列表推导式区别

>>> sum([i for i in range(100000000)])
>>>4999999950000000

>>> sum((i for i in range(100000000)))
>>>4999999950000000

虽然结果都能算出来两者之间,但是列表推导式耗时长、易卡顿,而生成器表达式计算速度快、不卡顿。

详细可参考 如何更好地理解Python迭代器和生成器?知乎

5 生成器函数的FAQ

1) 生成器函数可以带参数(带形参)吗?

生成器函数也是函数的一种,所以可以带参数

>>> def counter(start=0):
...   while True:
...     yield start
...     start += 1
...

这是一个从指定数开始的计数器。

2)生成器函数中可以有return 语句吗?

不可以,因为生成器函数已有默认了返回值——生成器,所以不能重新用return返回值(即使return None也不行),如果使用return返回值则会抛出语法错误异常。

>>> def gen():
...     print("输出1")
...     yield 1
...     print("输出2")
...     return "return语句"
...     yield 2
...     print("输出2")
...
>>> g = gen()
>>> next(g)
输出1
1
>>> next(g)
输出2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: return语句
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

参考

Python函数式编程指南(四):生成器

深入理解Python中的生成器

补充

1 沿着某条路线依次访问,且只访问一次,该种现象成为遍历

2 迭代器的访问是一次性的,指针向下移动时,不会后退;也即一次性调用。

3 生成器也是迭代器

def gen_ite():
    yield 1

it = gen_ite()
a = iter(it)

print(id(it))
print(id(a))

4 列表与生成器之间的关系

#生成一个列表库
>>> c = list(range(10))
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

#将一个新变量进行绑定一个新生成的序列
>>> c1 = [x for x in c]
>>> c1
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

#将一个新变量绑定生成器
>>> c2 = (x for x in c)
>>> c2
<generator object <genexpr> at 0x7fb2bf6f2518>
#生成器进行序列化
>>> list(c2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

#改变原变量c中键值为1的值
>>> c[1] = 100
#c1为新序列绑定的变量,改变原变量值对其没有影响
>>> c1
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
#c2其实是生成器中的第二种(列表推导式方式)产生生成器的方式(第一种是yield语句)。
#同时生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

#这里c2为空列表的主要原因是指针后移造成的
>>> c2
<generator object <genexpr> at 0x7fb2bf6f2518>
>>> list(c2)
[]
 

下段代码阐述生成器函数是按需产生结果

>>> c = list(range(10))
>>> c2 = (x for x in c)

>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> c[1] = 100
>>> c
[0, 100, 2, 3, 4, 5, 6, 7, 8, 9]

>>> next(c2)
0

>>> c
[0, 100, 2, 100, 4, 5, 6, 7, 8, 9]

#生成器随着原数据库变化而变化
>>> next(c2)
100
 
 
 
 

Python学习笔记014——生成器Generator的更多相关文章

  1. Python学习笔记之生成器、迭代器和装饰器

    这篇文章主要介绍 Python 中几个常用的高级特性,用好这几个特性可以让自己的代码更加 Pythonnic 哦 1.生成器 什么是生成器呢?简单来说,在 Python 中一边循环一边计算的机制称为 ...

  2. Python学习笔记014——迭代工具函数 内置函数enumerate()

    1 描述 enumerate() 函数用于将一个可遍历的数据对象(如列表.元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中. 2 语法 enumerate(sequ ...

  3. python学习笔记014——错误和异常

    Python有两种错误很容易辨认:语法错误和异常. 1 什么是语法错误 Python 的语法错误或者称之为解析错,是初学者经常碰到的,如下实例 if i>4 print("if语句输出 ...

  4. python学习笔记(四):生成器、内置函数、json

    一.生成器 生成器是什么?其实和list差不多,只不过list生成的时候数据已经在内存里面了,而生成器中生成的数据是当被调用时才生成呢,这样就节省了内存空间. 1. 列表生成式,在第二篇博客里面我写了 ...

  5. Python学习笔记014——迭代器 Iterator

    1 迭代器的定义 凡是能被next()函数调用并不断返回一个值的对象均称之为迭代器(Iterator) 2 迭代器的说明 Python中的Iterator对象表示的是一个数据流,被函数next()函数 ...

  6. Python学习笔记014——迭代工具函数 内置函数zip()

    1 描述 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表. 如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操 ...

  7. Python学习笔记(十一)

    Python学习笔记(十一): 生成器,迭代器回顾 模块 作业-计算器 1. 生成器,迭代器回顾 1. 列表生成式:[x for x in range(10)] 2. 生成器 (generator o ...

  8. Deep learning with Python 学习笔记(10)

    生成式深度学习 机器学习模型能够对图像.音乐和故事的统计潜在空间(latent space)进行学习,然后从这个空间中采样(sample),创造出与模型在训练数据中所见到的艺术作品具有相似特征的新作品 ...

  9. Deep learning with Python 学习笔记(6)

    本节介绍循环神经网络及其优化 循环神经网络(RNN,recurrent neural network)处理序列的方式是,遍历所有序列元素,并保存一个状态(state),其中包含与已查看内容相关的信息. ...

随机推荐

  1. 不错网络性能相关的文章-BaiduRPC

    http://wiki.baidu.com/display/RPC/Threading+Overview#ThreadingOverview-单线程reactor Threading Overview ...

  2. Android之AlarmManager

    Android平台中,Alarm Manager Service控制着闹钟和唤醒功能.和其他系统服务一样,提供了一个辅助管理类-AlarmManager,我们只需要使用AlarmManager即可调用 ...

  3. 浏览WPF中内置颜色名对应的颜色

  4. Note.js的stream用法一例

      Note.js,用stream读取文件的内容,注意decoder的用法 const fs = require('fs');   var rr = fs.createReadStream('data ...

  5. SharePoint 2013网站突然不能登录了。

    SharePoint 2013网站突然不能登录了,访问的时候,总是报错: The list has not shared with you.   原因: 原来我不知道什么时候把web applicat ...

  6. 在Java程序中做字符串拼接时一定要记得的MessageFormat.format

    Java里从来少不了字符串拼接的活,Java程序员也肯定用到过StringBuffer,StringBuilder,以及被编译器优化掉的+=.但这些都和下文要谈的无关. 比如有这样的字符串: 张三将去 ...

  7. 漫话Asp.net

    经过一段时间的接触,对asp.net这一块进行了很多其它的了解,漫话一下. Asp.net与Web : asp.net属于动态网页技术,属于web应用程序开发. Web应用程序通常是B/S模式. 和B ...

  8. Java从零开始学二十五(枚举定义和简单使用)

    一.枚举 枚举是指由一组固定的常量组成的类型,表示特定的数据集合,只是在这个数据集合定义时,所有可能的值都是已知的. 枚举常量的名称建议大写. 枚举常量就是枚举的静态字段,枚举常量之间使用逗号隔开. ...

  9. android设备上运行i-jetty服务

    android设备上运行i-jetty服务: 1) i-jetty安装 本人小菜一个,i-jetty源码有好几个文件,不知道怎么运行起来,于是找了一个现成可运行的i-jetty工程(感谢这位同学的分享 ...

  10. TQ2440之定时器中断0——volatile关键字的重要作用

    近日,在学习<ARM处理器裸机开发实战--机制而非策略>一书,在TQ2440开发板上,按照书中实例以及光盘配套程序源代码进行Timer0中断试验,编译成功后烧写到开发板上,没有任何反应,反 ...