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. nth-digit

    https://leetcode.com/problems/nth-digit/ public class Solution { public int findNthDigit(int n) { in ...

  2. iOS开发-多线程之GCD(Grand Central Dispatch)

    Grand Central Dispatch(GCD)是一个强有力的方式取执行多线程任务,不管你在回调的时候是异步或者同步的,可以优化应用程序支持多核心处理器和其他的对称多处理系统的系统.开发使用的过 ...

  3. 对 C# 未来的期望

    接触 C# 一年,总体上是一个非常完善的语言,但是某些细节特征还是不够完美.这里记下我现在对它将来的一些期望.       更强大的泛型约束   与 C++ 的模板相似,C# 的泛型使得编写适用于多种 ...

  4. WordPress后台的文章、分类,媒体,页面,评论,链接等所有信息中显示ID并将ID设置为第一列

    WordPress后台默认是不显示文章.分类等信息ID的,查看起来非常不方便,不知道Wp团队出于什么原因默认不显示这个但可以使用Simply Show IDs插件来实现 不使用插件,其他网友的实现: ...

  5. leetcode笔记:Sqrt(x)

    一. 题目描写叙述 Implement int sqrt(int x). Compute and return the square root of x. 二. 题目分析 该题要求实现求根公式,该题还 ...

  6. 【CSWS2014 Main Conference】Some Posters

    随机选了几张POSTER,之前没做过POSTER的同学可以看一下文字.图片.布局以及每个版块的小标题,以后如果需要做poster就容易多了. 据说这种Poster一张需要60RMB左右. 其中第5幅是 ...

  7. IOS企业开发者帐号申请

    想使用 XCode 的联机调试功能,必须先注册成为苹果开发者,再出99刀加入苹果 iOS 开发者计划才可以.加入苹果 iOS 开发者计划的方法 Google 一下就会找到很多链接.但是这些链接的内容都 ...

  8. mac 连接windows 共享内容

    mac 连接windows 共享内容 一:场景 在win7上下载了一个5G左右的系统文件,想弄到mac上,本打算用使用U盘,把文件从win7copy到mac电脑上: 可是U盘的分区是fat的,大于4G ...

  9. Centos6.6系统root用户密码恢复案例(转)

    原文:http://www.centoscn.com/CentOS/Intermediate/2015/0131/4604.html 通过单用户模式恢复root用户密码 重新启动主机后,在出现Grub ...

  10. #define中的“#”和“##”的作用

    在#define中,标准只定义了#和##两种操作: #用来把参数转换成字符串: ##则用来连接两个前后两个参数,把它们变成一个字符串.