Python学习之生成器

第一部分:生成器定义

生成器是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。调用该函数将返回一个可用于生成连续 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 time
start_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):
<generator object <genexpr> at   0x000001C71A4A1E40> ###print(a)生成的结果
  File "C:/Users/fuxiangyu/PycharmProjects/fuxiangyu/基础实验/标准输出.py", line 3, in <module>
    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__())

0
1
2
3
4
5
6
7
8
9

与上边生成列表的运算时间进行比对的例子

import time
start_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的详细讲解请https://blog.csdn.net/qq_33472765/article/details/80839417处仔细学习
可以简单的理解为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__方法,那么他就是迭代器

发布了24 篇原创文章 · 获赞 10 · 访问量 2370

猜你喜欢

转载自blog.csdn.net/flat0809/article/details/103045690