71

Python生成器的使用技巧详解

 5 years ago
source link: http://www.10tiao.com/html/384/201807/2651305851/1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

点击上方“Python开发”,选择“置顶公众号”

关键时刻,第一时间送达!















































































































































































































































































































    先不说楚枫的这般年纪,能够踏入元武一重说明了什么,最主要的是,楚枫在刚刚踏入核心地带时,明明只是灵武七重,而在这两个月不到的时间,连跳两重修为,又跳过一个大境界,踏入了元武一重,这般进步速度,简直堪称变态啊。


    “这楚枫不简单,原来是一位天才,若是让他继续成长下去,绝对能成为一号人物,不过可惜,他太狂妄了,竟与龚师兄定下生死约战,一年时间,他再厉害也无法战胜龚师兄。”有人认识到楚枫的潜力后,为楚枫感到惋惜。


    “哼,何须一年,此子今日就必败,巫九与龚师兄关系甚好,早就看他不顺眼了,如今他竟敢登上生死台挑战巫九,巫九岂会放过他?”但也有人认为,楚枫今日就已是在劫难逃。


    “何人挑战老子?”就在这时,又是一声爆喝响起,而后一道身影自人群之中掠出,最后稳稳的落在了比斗台上。


    这位身材瘦弱,身高平平,长得那叫一个猥琐,金钩鼻子蛤蟆眼,嘴巴一张牙带色儿,说话臭气能传三十米,他若是当面对谁哈口气,都能让那人跪在地上狂呕不止。


    不过别看这位长得不咋地,他在核心地带可是鼎鼎有名,剑道盟创建者,青龙榜第九名,正是巫九是也。


    “你就是巫九?”楚枫眼前一亮,第一次发现,世间还有长得如此奇葩的人。


    巫九鼻孔一张,大嘴一咧,拍着那干瘪的肚子,得意洋洋的道:“老子就是巫九,你挑战老子?”


    “不是挑战你,是要宰了你。”楚枫冷声笑道。


    “好,老子满足你这个心愿,长老,拿张生死状来,老子今日在这里了解了这小子。”巫九扯开嗓子,对着下方吼了一声。


    如果他对内门长老这么说话,也就算了,但是敢这么跟核心长老说话的,他可真是算作胆肥的,就连许多核心弟子,都是倒吸了一口凉气,心想这楚枫够狂,想不到这巫九更狂。


    不过最让人无言的就是,巫九话音落下不久,真有一位核心长老自人群走出,缓缓得来到了比斗台上,左手端着笔墨,右手拿着生死状,来到了巫九的身前。


    “我去,这巫九什么身份,竟能这般使唤核心长老?”有人吃惊不已,那长老修为不低,乃是元武七重,比巫九还要高两个层次,但却这般听巫九的话,着实让人吃惊不已。


    “这你就不知道了吧,巫九在前些时日,拜了钟离长老为师尊,已正式得到钟离长老的亲传。”有人解释道。


    “钟离长老?可是那位性情古怪的钟离一护?”


    “没错,就是他。”


    “天哪,巫九竟然拜入了他的门下?”


    人们再次大吃一惊,那钟离一护在青龙宗可是赫赫有名,若要是论其个人实力,在青龙宗内绝对能够排入前三,连护宗六老单打独斗都不会是他的对手。


    只不过那钟离一护,如同诸葛青云一样,也是一位客卿长老,所以在青龙宗内只是挂个头衔,什么事都不管,更别说传授宗内弟子技艺了,如今巫九竟然能拜入他老人家门下,着实让人羡慕不已。


    “恩怨生死台,的确可以决斗生死,但必须要有所恩怨,你们两个人,可有恩怨?”那位长老开口询问道。































































































0.本集概览

1.生成器可以避免一次性生成整个列表 

2.生成器函数的运行过程解析及状态保存 

3.生成器表达式的使用方法 

4.生成器表达式的可迭代特性

之前我们介绍了列表解析式,他的优点很多,比如运行速度快、编写简单,但是有一点我们不要忘了,他是一次性生成整个列表。如果整个列表非常大,这对内存也同样会造成很大压力,想要实现内存的节约,可以将列表解析式转换为生成器表达式。

今天这一集,就单聊生成器。

1.避免一次性生成整个列表

避免一次性生成整个结果列表的本质是在需要的时候才逐次产生结果,而不是立即产生全部的结果,Python中有两种语言结构可以实现这种思路。

一个是生成器函数。外表看上去像是一个函数,但是没有用return语句一次性的返回整个结果对象列表,取而代之的是使用yield语句一次返回一个结果。

另一个是生成器表达式。类似于上一小节的列表解析,但是方括号换成了圆括号,他们返回按需产生的一个结果对象,而不是构建一个结果列表。

这个“按需”指的是在迭代的环境中,每次迭代按需产生一个对象,因此,上述二者都不会一次性构建整个列表,从而节约了内存空间。

2.生成器函数

下面具体结合例子说说生成器函数。

2.1.运行过程分析

首先,我们还没有详细介绍过函数,先简单说一下,常规函数接受输入的参数然后立即送回单个结果,之后这个函数调用就结束了。

但生成器函数却不同,他通过yield关键字返回一个值后,还能从其退出的地方继续运行,因此可以随时间产生一系列的值。他们自动实现了迭代协议,并且可以出现在迭代环境中。

运行的过程是这样的:生成器函数返回一个迭代器,for循环等迭代环境对这个迭代器不断调用next函数,不断的运行到下一个yield语句,逐一取得每一个返回值,直到没有yield语句可以运行,最终引发StopIteration异常。看,这个过程是不是很熟悉。

首先,下面这个例子证实了生成器函数返回的是一个迭代器 代码片段:

  1. def gen_squares(num):

  2.    for x in range(num):

  3.        yield x ** 2

  4. G = gen_squares(5)

  5. print(G)

  6. print(iter(G))

运行结果:

  1. <generatorobjectgen_squaresat 0x0000000002402558>

  2. <generatorobjectgen_squaresat 0x0000000002402558>

然后再用手动模拟循环的方式来看看生成器函数的运行过程,你会发现和前面介绍过的熟悉场景并无二致。 代码片段:

  1. def gen_squares(num):

  2.    for x in range(num):

  3.        yield x ** 2

  4. G = gen_squares(3)

  5. print(G)

  6. print(iter(G))

  7. print(next(G))

  8. print(next(G))

  9. print(next(G))

  10. print(next(G))

运行结果:

  1. <generatorobjectgen_squaresat 0x00000000021C2558>

  2. <generatorobjectgen_squaresat 0x00000000021C2558>

  3. 0

  4. 1

  5. 4

  6. Traceback (most recent call last):

  7. File "E:/12homework/12homework.py", line 10, in <module>

  8. print(next(G))

  9. StopIteration

那这么看,在for循环等真正的使用场景中使用也不难了 代码片段:

  1. def gen_squares(num):

  2.    for x in range(num):

  3.        yield x ** 2

  4. for i in gen_squares(5):

  5.    print(i, end=' ')

运行结果:

  1. 014916

2.2.状态保存

我们进一步来说说生成器函数里状态保存的话题。在每次循环的时候,生成器函数都会在yield处产生一个值,并将其返回给调用者,即for循环。然后在yield处保存内部状态,并挂起中断退出。在下一轮迭代调用时,从yield的地方继续执行,并且沿用上一轮的函数内部变量的状态,直到内部循环过程结束。

关于这个问题,具体可以看看这个例子: 

代码片段:

  1. def gen_squares(num):

  2.    for x in range(num):

  3.        yield x ** 2

  4.        print('x={}'.format(x))

  5. for i in gen_squares(4):

  6.    print('x ** 2={}'.format(i))

  7.    print('--------------')

运行结果:

  1. x ** 2=0

  2. --------------

  3. x=0

  4. x ** 2=1

  5. --------------

  6. x=1

  7. x ** 2=4

  8. --------------

  9. x=2

  10. x ** 2=9

  11. --------------

  12. x=3

我们不难发现,生成器函数计算出x的平方后就挂起退出了,但他仍然保存了此时x的值,而yield后的print语句会在for循环的下一轮迭代中首先调用,此时x的值即是上一轮退出时保存的值。

3.生成器表达式

再说说生成器表达式吧。

3.1.使用方法

列表解析式已经是一个不错的选择,从内存使用的角度而言,生成器更优,因为他不用一次性生成整个对象列表,这二者之间如何转化呢?

生成器表达式写法上很像列表解析式,但是外面的方括号换成了圆括号,结果大不同,简单的看看: 代码片段:

  1. print([x ** 2for x in range(5)])

  2. print((x ** 2for x in range(5)))

运行结果:

  1. [0, 1, 4, 9, 16]

  2. <generator object<genexpr> at 0x0000000002212558>

方括号是熟悉的列表解析式,一次性返回整个列表,圆括号是生成器表达式,返回一个生成器对象,而不是一次性生成整个列表

3.2.适用于迭代环境

同时他支持迭代协议,适用于所有的迭代环境,略举几个例子: 代码片段:

  1. for x in (x ** 2for x in range(5)):

  2.    print(x, end=',')

运行结果:

  1. 0,1,4,9,16,

代码片段:

  1. print(sum(x ** 2for x in range(5)))

运行结果:

  1. 30

代码片段:

  1. print(sorted((x ** 2for x in range(5)), reverse=True))

运行结果:

  1. [16, 9, 4, 1, 0]

代码片段:

  1. print(list(x ** 2for x in range(5)))

运行结果:

  1. [0, 1, 4, 9, 16]

3.3.集合解析式与生成器对象

集合解析式等效于将生成器对象传入到list、set、dict等函数中作为构造参数 代码片段:

  1. set(f(x) for x in S if P(x))

  2. {f(x) for x in S if P(x)}

  3. {key:val for (key, val) in zip(keys, vals)}

  4. dict(zip(keys, vals))

  5. {x:f(x) for x in items}

  6. dict((x, f(x)) for x in items)




  • 作者:酱油哥

  • https://mp.weixin.qq.com/s/n0qvw5IUwFaOt4BgYc2Xyg

  • Python开发整理发布,转载请联系作者获得授权

【点击成为Java大神】


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK