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

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

我的邮箱是 snail_rush_sky@163.com


生成器都有什么用处

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

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

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

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

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

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

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

生成器类似

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

print(type(gen)) # <class 'generator'>

生成器的特性:

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

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

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

我们将所要保存的数据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, <built-in function print>)
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 异常。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容