深入理解Python生成器(Generator)
我们可以通过列表生成式简单直接地创建一个列表,但是受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,而且如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
|
1
2
3
4
5
6
|
>>> mylist = [ x for x in range(1, 10)] >>> mylist [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> gen = (x for x in range(1,10)) >>> gen <generator object <genexpr> at 0x7f1d7fd0f5a0> |
创建mylist和gen的区别仅在于最外层的[]和(),mylist是一个list,而gen是一个generator(生成器)。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过generator的next()方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> gen.next() 1 >>> gen.next() 2 >>> gen.next() 3 ... >>> gen.next() 9 >>> gen.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
其实我们可以使用for循环来代替next()方式, 这样才更符合高效的编程思路:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> gen = ( x for x in range(1, 10)) >>> for num in gen: ... print num ... 1 2 3 4 5 6 7 8 9 |
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
|
1
|
1, 1, 2, 3, 5, 8, 13, 21, 34, ... |
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
|
1
2
3
4
5
6
7
|
def fib(max): n = 0 a, b = 0, 1 while n < max: print b a, b = b, a + b n = n + 1 |
上面的函数可以输出斐波那契数列的前N个数:
|
1
2
3
4
5
6
7
|
>>> fib(6) 1 1 2 3 5 8 |
仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print b改为yield b就可以了:
|
1
2
3
4
5
6
7
|
def fib(max): n = 0 a, b = 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 |
这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
|
1
2
|
>>> fib(6) <generator object fib at 0x104feaaa0> |
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
举个简单的例子,定义一个generator,依次返回数字1,3,5:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
>>> def odd(): ... print 'step 1'... yield 1 ... print 'step 2'... yield 3 ... print 'step 3'... yield 5 ... >>> o = odd() >>> o.next() step 1 1 >>> o.next() step 2 3 >>> o.next() step 3 5 >>> o.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next()就报错。
回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()来调用它,而是直接使用for循环来迭代:
|
1
2
3
4
5
6
7
8
9
|
>>> for n in fib(6): ... print n ... 1 1 2 3 5 8 |
generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。
要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。
深入理解Python生成器(Generator)的更多相关文章
- python生成器(generator)、迭代器(iterator)、可迭代对象(iterable)区别
三者联系 迭代器(iterator)是一个更抽象的概念,任何对象,如果它的类有next方法(next python3)和__iter__方法返回自己本身,即为迭代器 通常生成器是通过调用一个或多个yi ...
- 彻底理解 Python 生成器
1. 生成器定义 在Python中,一边循环一边计算的机制,称为生成器:generator. 2. 为什么要有生成器 列表所有数据都在内存中,如果有海量数据的话将会非常耗内存. 如:仅仅需要访问前面几 ...
- 【Python注意事项】如何理解python中间generator functions和yield表情
本篇记录自己的笔记Python的generator functions和yield理解表达式. 1. Generator Functions Python支持的generator functions语 ...
- Python 生成器 (generator) & 迭代器 (iterator)
python 生成器 & 迭代器 生成器 (generator) 列表生成式 列表生成式用来生成一个列表,虽然写的是表达式,但是储存的是计算出来的结果,因此生成的列表受到内存大小的限制 示例: ...
- 【python之路29】python生成器generator与迭代器
一.python生成器 python生成器原理: 只要函数中存在yield,则函数就变为生成器函数 #!usr/bin/env python # -*- coding:utf-8 -*- def xr ...
- python 生成器generator
关于生成器,主要有以下几个 关键点的内容 一.什么是generator ,为什么要有generator? 二.两种创建生成器方式 三.yield关键字 四.generator 两个调用方法 next( ...
- Python 生成器 Generator 和迭代器 Iterator
#最近一周刚开始接触python,基本的语法,和使用特性和Java差别还是蛮大的. 今天接触到Python的迭代器和生成器有点不是很明白,所以搜索了先关资料整理了一些自己的理解和心得 简述(Profi ...
- python 生成器 generator
一.生成器定义 通过列表生成表达式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢? ...
- python 生成器(generator)的生成方式
generator包括生成器和带yield的generator函数. 写了一个生成杨辉三角的小例子: # -*- coding:utf-8 -*- def triangles(): l = [1] w ...
随机推荐
- Html 使用技巧 -- 设置display属性可以使div隐藏后释放占用的页面空间
div的visibility可以控制div的显示和隐藏,但是隐藏后页面显示空白: style="visibility: none;" document.getElemen ...
- imperva 非交互式导入导出配置
非交互使用模式full_expimp.sh可以导出/导入手动使用交互式CLI 在root的命令行下执行: 例子:导出:# full_expimp.sh --operation=1 --pwd=密码 - ...
- C#里partial关键字的作用
1. 什么是局部类型?C# 2.0 引入了局部类型的概念.局部类型允许我们将一个类.结构或接口分成几个部分,分别实现在几个不同的.cs文件中.局部类型适用于以下情况: (1) 类型特别大,不宜放在一个 ...
- Access中替代case when的方法 .
最近在做一个用Access的东东,其中用到了case when的方式,但是Access是不支持这种语法的,查询知道IIf和Swith可以作为替代,总结如下: IIf(expr, truepart, f ...
- 数据结构之队列(Python 版)
数据结构之队列(Python 版) 队列的特点:先进先出(FIFO) 使用链表技术实现 使用单链表技术,在表首尾两端分别加入指针,就很容易实现队列类. 使用顺序表list实现 # 队列类的实现 cla ...
- csslint在前端项目中的使用
大家都听说过jslint,eslint,不过你可能没见过csslint,你可能会问csslint有什么用,为什么今天要说csslint,是因为我在开发中遇到一个坑,其实之前不怎么使用csslint的, ...
- KVM virsh常用命令篇
1.查看运行的虚拟机 virsh list 2.查看所有的虚拟机(关闭和运行的虚拟机) virsh list --all 3.连接虚拟机 virsh console +域名(虚拟机的名称) 4.退出虚 ...
- 洛谷P1301 魔鬼之城
传送门啦 一道广度优先搜索的题目. 结构体含义: struct node{ int x,y,dir;//坐标,方向 int step;//当前步数 }; 方向的标号受上面定义的 $ dx[ ] , d ...
- mdb数据库文件如何导入Microsoft SQL Server 2008中
1.在‘开始’菜单打开‘Microsoft SQL Server 2008→SQL Server Management Studio’. 2.新建一个数据库(名称自定义),选择新建的数据库点击右键选择 ...
- python处理汉字转拼音pypinyin
主要是pypinyin 包,官网: http://pypinyin.readthedocs.io/zh_CN/master/index.html jieba包,主要是用来分词的,我之前的博文有介绍:h ...