本文共 5153 字,大约阅读时间需要 17 分钟。
生成器是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。调用该函数将返回一个可用于生成连续 x 值的生成【Generator】,简单的说就是在函数的执行过程中,yield语句会把你需要的值返回给调用生成器的地方,然后退出函数,下一次调用生成器函数的时候又从上次中断的地方开始执行,而生成器内的所有变量参数都会被保存下来供下一次使用。---------取自百度百科
接下来,我们回慢慢地理解到底什么是生成器!1下面来看一下列表生成器的例子
代码a=[i for i in range(10)] ###括号和括号内的内容构成一个列表生成器print(a) ###打印整个生成的列表print(a[0],a[1],a[2]) ###选择打印列表中的内容
执行结果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]0 1 2
分析:
首先我们用列表生成器生成了一个列表a,我们可以看到整个列表a的内容,我们也可以采取分片或者使用下标的方式来获取具体某个位置的值 那么这意味这什么呢? 意味着,在我们使用列表生成器生成一个列表的时候,列表生成器已经帮我们在内存中创建了内存空间,并将我们想要得到的值放入内存空间中。即他已经占用了我们的内存空间了! 我们测试的例子非常的小,如果我们要生成一个非常大的列表的话,比如说向这个列表中存入一亿个数字,首先他会占用极大的内存,并且会有一定的时间消耗,这样就会造成体验不佳的问题! 让我们来看一下效果 代码import timestart_time=time.time()a=[i for i in range(10000000)]end_time=time.time()print("time cost : {}" .format(end_time-start_time) )
执行结果
time cost : 0.8008153438568115
当我们要生成一个10000000个数的列表式,完成速度已经到了0.8S,这应该算是一个比较糟糕的等待体验了!
而且当输入的一个数比上述数据更大的时候,可能就会出现报错了,因为收到内存的限制,列表的容量肯定也是受限的而如果使用生成器的话,效率就会好很多,接下来,就让我们开始正式认识一下什么是生成器
结合我们上面举得例子,我们可以得知,使用列表生成器可以直接创建一个列表,但是会收到内存的限制,列表的长度会受限,而且会造成很大的资源浪费,假设我们创建一个列表我们只想使用他的前几个元素,那后面生成的元素无疑就是无用的
为了应对这种情况,我们就需要Python中一个非常重要的工具,生成器了!
生成器之所以可以解决这种问题,是因为,生成器创建一个列表,但是他并没有在立即在内存中开辟出内存空间并将数值存入,而是当调用到生成器的时候,才会生成对应的数值,这样无疑是最大限度利用了内存资源,也不会存在运行时间过长的问题!
下面我们来看一个实例
代码
a=(i for i in range(10)) ###()及里边的内容就可以看作一个生成器print(a)print(a[0],a[1],a[2])
执行结果
Traceback (most recent call last):at 0x000001C71A4A1E40> ###print(a)生成的结果 File "C:/Users/fuxiangyu/PycharmProjects/fuxiangyu/基础实验/标准输出.py", line 3, in print(a[0],a[1],a[2])TypeError: 'generator' object is not subscriptable
分析:可以看到当我们想要打印生成的列表时,出现了报错。
首先输出的是<generator object at 0x000001C71A4A1E40> ,表示这是一个内存地址,存放的是生成器generator! 然后我们再想打印a[0],a[1],a[2]的时候,就报错了,报错显示生成器不是可使用下标进行访问的这时就与我们所说的呼应起来了,生成器不是直接在内存中直接开辟空间并进行赋值,而是在内存中存放一个生成器,当我们要用到他的时候才会执行生成我们所需要的数据
下面看一个生成器简单使用的例子
a=(i for i in range(10))for i in range(10): print(a.__next__())或者a=(i for i in range(10))print(a.__next__())print(a.__next__())print(a.__next__())print(a.__next__())print(a.__next__())print(a.__next__())print(a.__next__())print(a.__next__())print(a.__next__())print(a.__next__())
0123456789
与上边生成列表的运算时间进行比对的例子
import timestart_time=time.time()a=(i for i in range(10000000))end_time=time.time()print("time cost : {}" .format(end_time-start_time) )
time cost : 0.0
通过上面两个例子,我们可以将上面讲的那些再一次的进行提炼,我们可以得到如下最终的结论
1.生成器不是直接在内存中开辟空间存数,而是在内存空间中放置一个生成器,只有我们在调用的时候,生成器才会生成我们想要的结果
2.并且整个生成器的执行可以理解为存在一个指针(可以参照上述两个例子中的第一个),指针只记录当前位置,当我们使用next()函数时,才会驱动指针的移动(生成器的继续运行),即生成器只记录当前位置!生成器的使用有两种方式 一种是我们最先使用的a=(i for i in range(10))生成一个列表 另一种就是接下来我们要介绍的在函数中添加yield使整个函数变成一个生成器
那么为什么生成器会有这样的效果呢?我们该如何编写一个生成器呢?下面我们来一起解决大家的疑惑
首先,我们来讲一下,为什么生成器会有这样的效果!那是因为存在yield函数,有关yield的详细讲解请处仔细学习
可以简单的理解为yield在python 里就是一个生成器。当你使用一个yield的时候,对应的函数就是一个生成器了。生成器的功能就是在yield的区域进行迭代处理。(迭代的问题后面我们会讲到)下面使用一个有趣的例子,帮助大家再深入的了解一下生成器
场景如下,使用生成器实现一个做包子和吃包子的过程 1.实现吃包子 代码def consumer(name): print("%s 准备吃包子了"%name) while True: type=yield ###生成器中最重要的一环 print("包子[%s]来了,被[%s]吃了!"%(type,name))c=consumer("fuxiangyu")c.__next__()c.__next__()c.__next__()
执行结果
fuxiangyu 准备吃包子了包子[None]来了,被[fuxiangyu]吃了!包子[None]来了,被[fuxiangyu]吃了!
分析:我们来分析一下整个流程是如何运作的
首先我们定义了一个吃包子的生成器,第一个next()函数,他执行了print("%s 准备吃包子了"%name)便停止了,此时我们再一次调用next()函数,他执行了print(“包子[%s]来了,被[%s]吃了!”%(type,name))这条命令,并再次进入while循环,等到再遇到yield时,再次停了下来,再从调用next()函数,还是重复上一次的操作,到yield停下,再等待next()函数的调用yield相当于是一个节点,也是一个变量,当程序运行到yield时,停止执行,也可以给yield赋值,让他输出我们想让他输出的东西
2.吃包子的优化-----send函数
def consumer(name): print("%s 准备吃包子了"%name) while True: type=yield print("包子[%s]来了,被[%s]吃了!"%(type,name))c=consumer("fuxiangyu")c.__next__()c.send("韭菜馅的")c.send("白菜馅的")
fuxiangyu 准备吃包子了包子[韭菜馅的]来了,被[fuxiangyu]吃了!包子[白菜馅的]来了,被[fuxiangyu]吃了!
这样我们就完成了一个吃包子的生成器函数了!接下来我们继续完成做包子这个函数,来实现单线程下的并行执行!
代码
def consumer(name): print("%s 准备吃包子了"%name) while True: type=yield print("包子[%s]来了,被[%s]吃了!"%(type,name))def producer(): c = consumer("fuxiangyu") c.__next__() while True: type=input("what type would you like:") print("we make a baozi that the type is {}".format(type)) c.send(type)producer()
执行结果
fuxiangyu 准备吃包子了what type would you like:韭菜we make a baozi that the type is 韭菜包子[韭菜]来了,被[fuxiangyu]吃了!what type would you like:白菜we make a baozi that the type is 白菜包子[白菜]来了,被[fuxiangyu]吃了!what type would you like:三鲜we make a baozi that the type is 三鲜包子[三鲜]来了,被[fuxiangyu]吃了!
分析:我们定义了两个函数,一个是生成器consumer,另一个是调用生成器的函数producer(可以看作有顾客的需求才会去生产)。然后我们执行producer函数
根据producer()函数中的定义,我们先创建一个生成器c(之所以要先进行一次next()函数是因为要先让他到达yield处,在yield处停顿) 然后将参数通过send()函数传入进生成器中,生成器中的yield接受到变量之后就开始执行,直到再遇到yield,就跳出生成器,返回原来的函数,继续执行!这样就实现了单线程下的并发,因为在我们使用者看来,producer()和consumer()函数是同时在执行的。当然实质上他们还是串行执行的,只是执行次序的变化让我们觉得像是并行执行一样
希望通过这个小例子能增深大家对生成器的理解!!!
接下来我们将要与生产器关系很密切的一个概念,迭代器!一.什么是迭代
迭代就是python中可以用for循环使用取值操作的过程 一.什么是可迭代对象(Iterable) 简单理解:可以被for这种循环语句执行完成的,就是迭代对象,比如List,tuple,dict,set,str等这些都算是迭代对象,迭代器也是迭代对象,因为迭代器也可以通过for循环来实现遍历判断一个对象是不是迭代对象的方法:
(1)如果他的使用方法中,有__iter__方法,那么他就是迭代对象二.什么是迭代器(Iterator)
简单理解:迭代器提供数据和数据的记录位置,更直接点,可以使用__next__方法的就是迭代器。迭代器可以认为是迭代对象的子集判断一个对象是不是迭代器
(1)如果他的使用方法中,有__next__方法,那么他就是迭代器转载地址:http://zwttd.baihongyu.com/