你常看到 Python 代码中的 yield 到底是什么鬼?

学习python的正确姿势

共 1789字,需浏览 4分钟

 ·

2019-12-20 23:28


之前给大家说过,久久会解锁一篇 VIP 中的文章给大伙乐呵乐呵,其实是被吐槽没有试看文。




41be5ac619184a0658612a7f936697d7.webp





好吧,今天就是这么出其不意攻其不备,就随便挑选一篇出来给你瞧瞧货色: yield 到底是干屌的?



首先我们来看下这两个列表的区别:




a17e2dd68c8e85229d434795668f2a09.webp



可以看到, list_1 是 [i for i in range(20)] ,而 list_2 是 (i for i in range(20)),主要区别就是一个是 [] , 一个是 (),而后者就是生成器。

可以看到,当我们去执行 list_2 的时候,输出结果是一个 generator(生成器)对象。

我们可以通过 next() 去调用这个生成器里面的元素:


a79bfb9150220f29d0179a39b47ea94f.webp




我想到这里你会有点疑惑,就是它们之间有什么区别?

我们这样,来计算一下以下这两个列表生成所需要的时间:

list_1 = [i for i in range(66666666)]list_2 = (i for i in range(66666666))

我们分别把它们放到方法里面执行,然后用装饰器的方式对它们各自的方法执行时间做个记录:


2fe8797df1c34473298e1a7ffcc66b97.webp



执行结果如下:


91fedca38466440cba4daf9f9af8a2f2.webp


你会发现,方法二,也就是生成器生成的列表 list_2 所消耗的时间远低于 list_1。

这是因为使用列表生成式生成的列表,是固定的,也就是说,当我们执行 [i for i in range(66666666)] 的时候, list_1 这个列表所封装的元素个数就是 66666666 -1 个。

而生成器生成的 list_2 就不是这样,它是记录了一定的算法规则,比如我们这里的 for i in range(66666666) 就是不断的 +=1 ,生成器只要记住这个规则就可以了,不需要将全部的数据都直接一股脑的装进 list_2 ,而是等到我们需要的时候,我们去调用的时候,它再去算一下,然后把相应的数据返回给我们。

这,就是生成器,记住一点,它是根据一定的规律算法生成的,当我们去遍历它的时候,它可以通过特定的算法不断的推算出相应的元素,边运行边推算结果,从而节省了很多空间。

体会下下面这两者的区别:


ba3c14328da9053705d2aa5a8a52d88e.webp



知道了什么是生成器之后,咱们再来看看这个魔法关键词:yield。

yield 和 return 有点相似,return 这个关键词你应该很熟悉吧,在函数中只要执行到这个关键词,就会返回相应的结果并且终止函数的执行:

8904d58a832dfdc20f771a6440223c37.webp



很好理解,这里返回 3 而不会继续返回 hahahahah。

那么 yield 又是怎么样的呢?咱们继续看:


f963602a4ca152b4e7960c8086be95e7.webp


在这里面我们定义了一个 foo 方法,并且在里面定义了好几个 yield ,可是当我们调用这个方法的时候,居然跟什么都没有输出。

那么我们来看看调用了这个 foo 函数之后得到的是什么类型:

ffcb52c261ad0fafc1d72dba4cfa033e.webp



可以看到,它是一个生成器!

是的,一个函数里面如果被定义了 yield ,那么这个函数就是一个生成器。

我们可以通过 next 方法来使用这个生成器:

d236c65f584b74df7e0ad04d1d2e63ef.webp



可以看到,当我们第一次调用的时候,遇到第一个 yield 的时候就跳出函数了,第二次调用的时候就是从第 1 个 yield 开始,第三个 yield 结束,以此类推。直到 next 没有数据之后报 StopIteration。

既然它是一个生成器,那么我们就可以遍历它,我们具体打印出每个具体的元素,看看是什么:

935bbef197e60688931079a9d8a1026f.webp



可以看到我们这里得到的都是 None ,这是因为我们 yield 的时候没有返回具体数据,我们可以这样:

4790d863fd78ab075865529451a30a89.webp



这样我们就可以得到的生成器里面具体的元素了。

不过呢,我们一般不会这么写,我刚刚说了,生成器是根据一定的规律算法生成的列表,当我们去遍历它的时候,它可以通过特定的算法不断的推断出相应的元素,边运行边推算结果,节省空间。

所以 yield 一般会在 for 循环语句以一定的规则生成,举一个最简单的例子,生成这么些偶数:



80ce922752a8f3a60e6d7e227f146053.webp



这样我们在调用 foo 的时候就会得到一个生成器,然后就可以直接遍历使用。另外,yield 后面可以跟常用的数据类型,比如 string,int,dict:


43840cf1e2174963830d00d8927bfa95.webp


那么当你下次去调用相应的函数就会得到这样的 generator,于是就可以:OMG,根据对应的规则遍历它! 


ok,以上就是生成器和 yield 的用法,你以后还会经常看到它的身影,我们下回见,peace!




1fb66ea2bd06c9c48bc56866a25aae72.webp


扫一扫

学习 Python 没烦恼




浏览 12
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报