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. [11] 楔形体(Wedge)图形的生成算法

    顶点数据的生成 bool YfBuildWedgeVertices ( Yreal width, Yreal length, Yreal height, YeOriginPose originPose ...

  2. Vue Cli安装以及使用

      因为公司项目要用vue框架,所以会用vue-cli来新建项目.用过vue的都知道,要全局安装vue以及脚手架vue-cli,然后执行vue init webpack projectname来新建v ...

  3. 介绍两个非常好用的Javascript内存泄漏检测工具

    内存泄漏对开发者来说一般很难检测因为它们是由一些大量代码中的意外的错误引起的,但它在系统内存不足前并不影响程序的功能.这就是为什么会有人在很长时间的测试期中收集应用程序性能指标来测试性能. 最简单的检 ...

  4. python3 识别图片文字

    1.下载相关组件 安装:pip install Pillow 或下载:https://pypi.org/project/Pillow/5.2.0/  ,https://pypi.org/project ...

  5. C++的四种初始化形式以及类型转换

      C++中有如下的方式来初始化一个变量. 但当进行类型转换时,只有两种方式可用,其他两种方式会报错.

  6. wepy - 与原生有什么不同(x.wpy)使用实例

    源码 <template> <view class='mark' wx:if="{{showMark}}"> <view animation=&quo ...

  7. JSTL 标签库 使用(web基础学习笔记十九)

    标签库概要: 一.C标签库介绍 1.1.<c:> 核心标签库  JSTL 核心标签库(C标签)标签共有13个,功能上分为4类:1.表达式控制标签:out.set.remove.catch2 ...

  8. Openerp 7.0消息推送

    在一个文档的state变化时,需要将变化情况告知关注用户,通过研究account.invoice的代码,发现是经过如下过程实现此功能的: 1.添加一个消息阶段: <record id=" ...

  9. 如何固定OpenERP顶的主菜单,方便滚动至第二屏以及多屏时,快速切换主菜单

    如何固定OpenERP顶的主菜单,方便滚动至第二屏以及多屏时,快速切换主菜单 作者:广州-步科,来自OpenERP应用群() 将“addons\web\static\src\css”目录下的“base ...

  10. Android 英文文档下载地址

    通过英文Android API学习Android技术是一个不错选择,当然养鸡的专业户要小心了,以下分享一些下载英文文档的链接(请使用迅雷下载): https://dl-ssl.google.com/a ...