关于python3中的生成器的介绍与理解

首先说明一下,第一次写博客,难免会有纰漏,如果有不足的地方希望大家指正,谢谢

我的邮箱是 [email protected]


生成器都有什么用处

生成器可以用来存放一些数据,像列表元组那样,在生成器的高端用法里被用作协程(py3.4以前),可以用来实现一些异步操作。

哪些人会用到生成器,在什么场景下来用呢

在你有很多很多数据要存放在一个容器中时,但你并不着急需要用这些数据(也就是说这些数据可能会占用你的大部分内存),那么你很可能需要用到生成器。

或者当你在编写异步IO的项目时,你几乎逃不掉的要学习生成器,还有很多场景下都会用到生成器

那么如何来创建一个简单的生成器呢

很简单,就像一个列表推导式那样

lst = [i for i in range(5)] # 这样就快速创建了一个列表

生成器类似

gen = (i for i in range(5)) # 同样我们创建了一个生成器,这个生成器是一个可迭代对象

print(type(gen)) #

生成器的特性:

生成器可以认为是一个存放数据的集合,而且他是一个一次性的容器

当然这很简单,但是不要简单的认为,这就像快速生成了一个元组,千万不要这么认为,

因为这根本就不是一个元组。

我们将所要保存的数据0,1,2,3,4保存在了这个生成器中

那么当我们需要用到这些数据的时候,我们怎么将它们取出呢?我们不能在像以前的方式
通过下标来取出里边的值,那样是毫无结果的。
上文说过,生成器是一个可迭代对象,那么我们就可以用for..in结构,来一个一个的取出里边的元素,如:

for one in gen:  # 遍历这个生成器,并将返回值打印出来
  print(one)
0
1
2
3
4

当然这种做法也是很安全的,除此之外还有一种方法next(),

newgen = (i for i in range(2))
print(next(newgen))  # 0  注意需要重新在创建一个新的生成器,
# 接下来会作说明 
print(next(newgen))  # 1

每调用一次这个方法将会取出这个集合里边的一个元素

但是,当取到最后一个元素后,如果对这个生成器使用next()方法将会引发StopIteration异常

print(next(newgen)) # StopIteration

这是为什么呢,因为生成器是一次性的,不回头的,这里是相对于像列表,元组,字符串等那样的集合类型而言

列表等集合是可以多次进行迭代(就是可以多次的遍历集合中的元素)的

而生成器不行,你可以理解生成器为 一个装有巧克力豆的盒子,每次用next()方法时都会从这个盒子中拿走,
真正的拿走一颗巧克力豆,那么这个盒子里的巧克力豆就会真正的减少一颗,当把最后一颗巧克力豆拿走时,
如果你再来拿,那么就会抛出异常,因为盒子都没有巧克力豆了,还怎么拿。
而像列表那样的集合也可以认为是装有巧克力豆的盒子,但是这个盒子里的东西只能看,不能带走,每次看完之后还得按照原来的顺序把它放回去。
那么你应该对生成器有一定的了解了吧。
另外,我们还可以玩一点更厉害的,希望你学了函数,那么继续介绍一下

def gen(n):
  # 在这里你可以做更多的别的厉害的操作,
  # 而不是只是像用生成器表达式那样
  # 只是做有限的操作,示例如下
  for i in range(n):
    yield i  # 注意在函数中如果出现了yield(这是一个关键字),
               # 那么这个函数就是一个生成器函数,
               # 可以对其进行一些向生成器那样的操作,如next()等。
g = gen(3)  # 同生成器一样,需要创建一个生成器对象,
                   #然后就能对其进行操作了 
for one in g:
  print(one, end=' ')
# 0 1 2

小结一下
生成器函数比生成器表达式能玩出更多的花样,在yield 之前可以做很多事,另外在生成器函数中并没有限定只能有一个yield,可以多次yield,也可以出现return,但是在函数中遇到return ,那么函数就是调用结束了,我在类比一下,::
在生成器函数中
可以把函数认为是一场 需要做的任务,这个任务的量很大,但是在完成整个任务前必须要提交一些已经完成的事情,在任务的最后必须要提交任务结果(这场任务的最终结果)
------------------------------------------------------------------类比下:

import time

def demo():
  work = 10
  now = 0
  time.sleep(1)  # 模拟工作时长
  now += 3
  yield now  # 目前还没有完成最终的任务,先提交一部分
  time.sleep(1)  # 同上  
  now += 6
  yield now  # 还没做完,再提交一部分
  time.sleep(0.2)
  return now  # 终于做完了,提交最终的结果
  yield 123  # 这些都不会执行,因为return 就是函数的终点
  print('hello world')  # 也不会执行

注意,当函数运行到return时,这个函数就算结束了,即使下面还有代码有不会执行到,如上边注释的例子。

除此之外,对一个生成器对象还有其他的一些函数可以使用,如send(),throw()等
接下来我来逐一介绍
send()顾名思义,就是向生成器里送东西,注意,在执行这个函数前需要明确知道,在生成器内部运行到哪了,这个哪一共有三种状态,以一种是目前生成器还没有启动,那么这个时候就不能向生成器里送除了None以外的任何值,否则将会引发异常,PS:我认为next()方法就相当于send(None)方法,send()方法也是拨动一下这个生成器,直到这个函数运行到下一个yield或者return为止。
第二种是这个生成器已经运行到某一个yield了,那么现在就可以往这个生成器里送东西了,实例代码如下:

def gen(n):
    for i in range(n):
        print(i)
        print('*'*20)
        recv = yield i
        print(recv)
        print('#'*20)

g = gen(3)
result = g.send(None) # next(g)
g.send(22)
print(result)
# 运行结果如下:
0
********************
22
####################
1
********************
0

由此可以发现,当send(None)后,生成器运行到第一个yield,并且这个函数暂时交出cpu的控制权(完成一部分想歇会,暂时不干了),函数内部的print(i) 最先执行,然后打印出了*号,这时运行到yield,因为yield 也可以将某些数据带出去,这个时候result就接收了刚才yield出来的值,也就是最后打印出来的0,因为第一次的i就是0嘛。这个时候已经从函数中出来了,要运行g.send(22),这样就把22这个数据带进了生成器的内部,&&PS:我认为这个一点很强,因为这就相当于一个内部的函数啊,那就可以送进去几乎是任何类型的数据了,无论字典元组字符串列表,甚至是函数,类都可以,但是我很少,基本没见过有人这么用过,可能没必要这么做。示例代码如下:等会再上代码。PS结束&&
送进去的22会被recv接收(可以认为那个就是赋值语句,从右往左执行)在print(recv) 就会打印22,然后再打印#号,由于这个循环还没有运行完,将再循环一次,再打印1,这时候i变成1了嘛,再打印*号,遇到yield就返回,这就是一个比较复杂的生成器函数。
第三种情况就是这个生成器出现了异常,那么将无法对这个生成器使用任何的方法
PS: 上边说的示例代码:

def gen(n):
    for i in range(n):
        print(i)
        print('*'*20)
        recv = yield i
        print(recv)
        recv[1]('hello world')
        print('#'*20)
        
g = gen(3)
g.send(None) # next(g)
g.send((1,print))
# result ---------------------------------------
0
********************
(1, )
hello world
####################
1
********************

另一个方法throw()顾名思义,抛出点什么东西,那就是抛出异常:
就是将这个生成器给玩坏,这个方法很简单,具体的实际用处在将来要介绍的协程中会用到,在生成器内部抛出异常,终止生成器的操作。
简单使用直接上代码

def gen(n):
    for i in range(n):
        print(i)
        yield i
       
g = gen(5)
g.send(None) # next(g)
g.send(1)
try:  # 捕获异常,这里一定会产生异常
    g.throw(Exception('参数是一个异常类'))
except:
    pass
#  注意,如果不是手动用throw()把生成器玩坏的话,那么此时这个生成器应该还没#  走到头(巧克力豆还没全部拿完)。但是现在已经把这个盒子给弄坏了,所以如#  果在来盒子里拿东西,将会触发异常,盒子都坏了,你还拿什么东西?
g.send(1)
抛出 StopIteration 异常。

我所知道的生成器的简单应用就这些,如果大家还有什么补充的话可以通过邮箱或者来通知我,希望大家能一起进步。
如果感觉读我的文章能读懂的话,可以点个赞支持一下哦,谢谢
感觉我的例子不是很贴切模型的话也可以在下方评论,希望大家能指出我的不足之处,如果对我有什么建议及意见也要指出来哦,比如再写点别的知识之类的。
总之谢谢各位的耐心观看 _

你可能感兴趣的:(关于python3中的生成器的介绍与理解)