生成器

1. 什么是生成器

大家知道通过列表生成式(不知道的可自行百度一下),我们可以直接创建一个列表,但是,受内存限制,列表内容肯定是有限的。比如我们要创建一个包含100万个元素的列表,这100万个元素会占用很大的内存空间,而且如果我们仅仅需要访问前面几个元素的话,那后面绝大多数的元素占用的空间就都白白浪费了。设想一下如果列表中的元素能够在循环使用的过程中推算出来,即用一个推算一个,这样是不是就不用一次性生成全部元素从而大大节省了内存空间呢。在python中这种一边循环一边计算的机制称为生成器:generator。

2. 创建生成器的方法

这里主要介绍2中创建生成器的方法:

方法一:这种方法最简单,只要把一个列表生成式的[]改成()即可,如下:

>>>L = [x*2 for x in range(10)]

>>>L

>>>[0,2,4,6,8,10,12,14,16,18]

>>>G = (x*2 for x in range(10))

>>>G

>>><generator object <genexpr> at 0x7f628d120kb0

创建L和G的区别仅在于最外层的[]和(),但结果是L是一个列表而G是一个生成器,我们知道列表我们可以直接打印出来,那么生成器的值我们应该怎么获取呢,同样有2中方式,第一是通过next()函数获得生成器的下一个返回值:

>>>next(G)

>>>0

>>>next(G)

>>>2

但是这样获取太过麻烦,如果像上面说的需要100万个元素时我们这样写岂不是会累死,而且如果到最后没有元素还会产生StopIteration的异常。

下面说一下获取生成器值的第二种方法:使用for循环,因为生成器也是可迭代的对象,我们创建一个生成器后基本是不会使用next函数调用的,而是通过for循环来迭代,并且不用关心StopIteration异常。

方法二:使用yield关键字创建生成器

方法一种我们介绍了最简单的生成方法,是把列表生成式的[]改为(),这种方式适合推算算法比较简单的场景。试想如果推算算法和复杂,用类似列表生成式一行代码无法实现的时候该怎么办呢?幸运的是generator非常强大,可以把一个函数作为一个生成器。比如著名的斐波那契数列,除第一和第二个数一样外,其它任意一个数都是由前两个数相加而得到:1,1,2,3,5,8...

这时列表生成式就无法满足了,但是我们可以用函数很容易的打印出来:

def fib(times):
n = 0
a,b = 0,1
while n < times:
print(b)
a,b = b,a+b
n+=1
return 'done'
#调用fib函数生成5个数
fib(5)
#输出结果如下:
1
1
2
3
5
done

可以看出,fib函数定义了斐波那契数列的推算规则,可以从第一个元素开始推算出后续任意的元素,这种逻辑非常类似生成器generator,也就是说上面的函数离生成器仅一步之遥。这里只需要把print(b)改为yield b就变成一个生成器了。

def fib(times):
n = 0
a,b = 0,1
while n < times:
yield b
a,b = b,a+b
n+=1
return 'done' for x in fib(5):
print(x) #输出结果如下:
1
1
2
3
5

但是这里有个问题就是用for循环调用generator时就无法获取到生成器中的return返回值了,如果想要获取返回值,还得借助next函数并捕获StopIteration异常,返回值则包含在StopIteration的value中,如下:

g = fib(5)
while True:
try:
x = next(g)
print(x)
except StopIteration as e:
print("生成器的return返回值:%s" % e.value)
break #输出结果:
1
1
2
3
5
生成器的return返回值:done

3. yield关键字

接下来说一下yield关键字,这里暂时可以把yield理解为return,然后我们用一个列子来说明yield与return的区别。

def fun():
print("====start=====")
while True:
res = yield 8
print('res: %s' % res) g = fun()
print(next(g))
print('*'*20)
print(next(g)) #输出结果如下
====start=====
8
********************
None
8

代码解读:首先创建一个生成器。接下来当程序运行到g = fun()时,实际上并不会去执行fun里面的代码,而是相当于创建了一个生成器对象并赋值给g。程序继续往下执行,当运行到第一个print(next(g))时,因为遇到了next函数,fun中的代码开始执行,首先打印输出====start=====,然后进入while循环执行yield 8,前面说过yield有return的功能,这时程序将数字8return出去,然后程序停止,fun中yield后面的代码不会被执行(注意这里执行完yield 8之后,fun就已经停止往后运行,并不会执行赋值操作,也就是说没有将8赋值给res),所以这里我们看到的输出结果是====start=====和数字8

第二步程序继续执行print(‘*’*20)然后输出20个*

第三步当程序遇到第二个next函数时,跟上面那个next差不多,但不同的是,这个时候程序将从上一个next停止的地方开始执行,也就是说程序又跳回到res = yield 8这一行并且开始执行赋值操作,然而在刚刚执行第一个next的时候=右边的值已经被return出去了并没有执行赋值操作,所以这个时候右边并没有值,因此这个时候res被赋值为None,所以我们看到程序输出为None

第四步程序继续执行while循环,又遇到yield关键字然后同样将8return出去程序停止,所以None后面又输出一个8

这就是yield被return的区别,带yield函数就是一个生成器,生成器有个next函数,当第一次调用next函数时,程序将从生成器fun的开始执行,当继续调用next 时,这一次的next 将从上一次的next停止的地方开始执行,也就是从yield关键字的地方开始执行,而并不是每次next都从生成器的开始执行,然后遇到yield后把要生成的值return出去程序停止(这里的程序停止指的是生成器中yield后面的代码不会执行,而不是整个程序停止)。

那么如果我们想要接收res的值应该怎么办呢?接下来介绍生成器的另外一个函数send函数。

4. send函数

我们将上面的代码修改一下

def fun():
print("====start=====")
while True:
res = yield 8
print('res: %s' % res) g = fun()
print(next(g))
print('*'*20)
print(g.send(5)) #输出结果如下
====start=====
8
********************
5
8

看一下这里只是将原来第一个print(next(g))改为print(g.send(5)),那么res输出结果由原来的None变成了5。这是为什么呐,原来在调用send函数时会将所传递的参数发送给生成器并赋值给res,上面说过return的时候并没有把8赋值给res,下次执行的时候由于值已经被return出去所以只好将None赋值给res。而如果用send的话,开始执行的时候与next一样,也是先从上一次停止的地方开始执行,不同的是send会先把发送过去的参数5赋值给res,然后再继续往后执行,遇见下一回的yield,return出结果后结束。

总结

生成器是这样一个函数,它记住上一次返回时在函数体中的位置,对生成器函数的第二次或第n次调用跳转至该函数上次停止的地方,而上次调用的所有局部变量都保持不变。

生成器的特点:

1. 节约内存

2. 迭代到下一次调用时,所使用的参数都是第一次所保留下的,也就是说在整个函数调用的所有的参数都是第一次调用所保留的而不是新创建的。

Python核心编程之生成器的更多相关文章

  1. Python核心编程的四大神兽:迭代器、生成器、闭包以及装饰器

      生成器 生成器是生成一个值的特殊函数,它具有这样的特点:第一次执行该函数时,先从头按顺序执行,在碰到yield关键字时该函数会暂停执行该函数后续的代码,并且返回一个值:在下一次调用该函数执行时,程 ...

  2. python核心编程第二版笔记

    python核心编程第二版笔记由网友提供:open168 python核心编程--笔记(很详细,建议收藏) 解释器options:1.1 –d   提供调试输出1.2 –O   生成优化的字节码(生成 ...

  3. 学习《Python核心编程》做一下知识点提要,方便复习(一)

    学习<Python核心编程>做一下知识点提要,方便复习. 计算机语言的本质是什么? a-z.A-Z.符号.数字等等组合成符合语法的字符串.供编译器.解释器翻译. 字母组合后产生各种变化拿p ...

  4. python核心编程--笔记

    python核心编程--笔记 的解释器options: 1.1 –d   提供调试输出 1.2 –O   生成优化的字节码(生成.pyo文件) 1.3 –S   不导入site模块以在启动时查找pyt ...

  5. Python核心编程第二版(中文).pdf 目录整理

    python核心编程目录 Chapter1:欢迎来到python世界!-页码:7 1.1什么是python 1.2起源  :罗萨姆1989底创建python 1.3特点 1.3.1高级 1.3.2面向 ...

  6. python核心编程--笔记(不定时跟新)(转)

    的解释器options: 1.1 –d   提供调试输出 1.2 –O   生成优化的字节码(生成.pyo文件) 1.3 –S   不导入site模块以在启动时查找python路径 1.4 –v   ...

  7. python核心编程笔记(转)

    解释器options: 1.1 –d   提供调试输出 1.2 –O   生成优化的字节码(生成.pyo文件) 1.3 –S   不导入site模块以在启动时查找python路径 1.4 –v   冗 ...

  8. Python核心编程(第二版)PDF

    Python核心编程(第二版) 目录 第1部分 Python核心第1章 欢迎来到Python世界1.1 什么是Python1.2 起源1.3 特点1.3.1 高级1.3.2 面向对象1.3.3 可升级 ...

  9. 拒绝从入门到放弃_《Python 核心编程 (第二版)》必读目录

    目录 目录 关于这本书 必看知识点 最后 关于这本书 <Python 核心编程 (第二版)>是一本 Python 编程的入门书,分为 Python 核心(其实并不核心,应该叫基础) 和 高 ...

随机推荐

  1. 使用PXE+VNC方式安装CentOS 7

    U盘坏了,用个下面的方法安装 安装配置dhcp yum -y install dhcp tftp-server 修改如下,网段改为你自己的网段 vim /etc/dhcp/dhcpd.conf sub ...

  2. [CF664A]Complicated GCD(数论)

    题目链接 http://codeforces.com/problemset/problem/664/A 题意 给两个数,找出它们的最大公因子d,使得从a到b之间的数都可以整除d. 题解 结论: 当gc ...

  3. JVM关于GC的日志分析

    通过阅读GC日志,我们可以了解Java虛拟机内存分配与回收策略.内存分配与垃圾回收的参数列表 一XX: +PrintGC 输出Gc日志.类似: 一verbose:gc 一XX: +PrintGCDet ...

  4. xml的复习

    xml的复习 1.概念:可扩展标记语言 2.功能: *存储数据    1.配置文件     2.在网络中传播 3.xml与html区别: xml语法严格,HTML语法松散 xml存储数据,HTML展示 ...

  5. openstack核心组件——nova计算服务(7)

    云计算openstack核心组件——nova计算服务(7)   一.nova介绍:       Nova 是 OpenStack 最核心的服务,负责维护和管理云环境的计算资源.OpenStack 作为 ...

  6. python中的画笔控制函数

    蟒蛇绘制代码中的画笔控制函数 penup() ,pendown() ,pensize() , pencolor()函数 这里就将海龟想象成画笔 画笔控制函数,画笔操作后一直有效,一般成对出现 将画笔抬 ...

  7. 轻轻松松学CSS:overflow

    一.overflow的定义 overflow,音[əʊvəˈfləʊ],义[溢出],就像2.2米的人躺在1.8米的床上,腿得耷拉到床外一样.overflow 属性用于控制内容溢出容器时显示的方式 二. ...

  8. 系统模块sys的常用方法

    sys.argv参数 sys.argv  等价于是一个列表,argv[0] 代表当前脚本程序路径名,可以通过在外部获取多个参数传递给脚本程序执行,按照列表的取值方式第二个数据就是sys.argv[1] ...

  9. 详细分析 Java 中实现多线程的方法有几种?(从本质上出发)

    详细分析 Java 中实现多线程的方法有几种?(从本质上出发) 正确的说法(从本质上出发) 实现多线程的官方正确方法: 2 种. Oracle 官网的文档说明 方法小结 方法一: 实现 Runnabl ...

  10. 002 01 Android 零基础入门 01 Java基础语法 01 Java初识 02 Java简介

    002 01 Android 零基础入门 01 Java基础语法 01 Java初识 02 Java简介 学习Java的基础语法 Java是一门编程语言,学习的逻辑其实和现实世界的语言是一样的,需要了 ...