目录
-
生成器介绍
生成器是拿来干嘛的?没有生成器之间,我们有一个东西叫列表解析式,说白了,就类似如下:
a=[i for i in range(999999)]
它有啥优点?适合新人入门,运行速度快,易于编写。但缺点呢?我们可以理解为一下子生成了整个列表并加载到内存中。
也就是吃内存,后面的同步代码要等你加载完才能运行。
那再说生成器,一个是带个yield就搞定,一个是圆括号形式,就是如上的中括号换成圆括号,如下:
a=(i for i in range(999999))
本质就是,它自带了一个"return"。这个return每次只返回一个对象。它不会一次性构建整个列表,而是通过迭代的方式去惰性的加载。打个比方就是。所有的包子都做好了,但是有人下单,我才会去烤箱加热,然后卖出去。而不是全部先放蒸笼保温,那样我的蒸笼(内存)就那么大,我就不能腾出空间(cpu,内存)去做其他事情了。所以它做了2件事情:保存运行的状态,返回值.
我们来看看a是什么:
返回的是一个迭代器,也就是它不会返回给你一个具体的结果,并且它本质就是一个迭代器。你需要调用for循环来迭代输出,而for循环就自动帮你调用了next函数来迭代输出。而yield也是如此。不外乎隐式或者显式的调用next。
-
yield使用
yield相关我们常用的有next,send,__next__,close等
next和__next__的作用就是按需取下一个值。close就是销毁了这个生成器。send的作用是给yield的左边传参。当然第一次调用的时候,因为yield的执行顺序是先右边再左边,所以,我们遇到yield就停止了运行,此时send是无法给左边赋值的,那我们第一次调用只能send(None)。send其他就给next保持一致了。我们这里举个小小的例子吧
def gen_test():
value = 0
while True:
s = yield value
print('s=', s)
value += 1
m = gen_test()
m.send(None)
for i in range(10):
m.send(i)
下面我们运行看看效果:
这里我们每次有个参数s,它是用来接收send传值的。
至于为什么第一次使用None进行传值,是因为第一次我们遇到yield就会暂停。而并没有进行赋值操作。所以我们传None。
当然,这只是我们的猜测,具体是不是赋值是在第二次完成,我们可以进行一个小小的实验看看效果。
def gen_test():
value = 0
while True:
s = yield value
print('s=', s)
value += 1
m = gen_test()
m.send(None)
print('进行第二次send!')
m.send(1)
OK,看输出结果。确实是在第二次send的时候我们才能接收到send的值,而第一次必须传None,我们从他的祖先去看看。
OK,看了下它的底层,确实是必须在一个生成器第一次迭代的时候必须传None。
-
yield from的用法
下面介绍下yield from。要用yield from,一样的,需要实现__iter__和__next__ 方法,至于为什么他们对照的是for循环和next。则是对接的相应的协议,这里我们就无须深究了。
我们先看看它是否包含了这2个方法。依然是通过代码来举例:
def gen_test(args):
yield from args
if __name__ == '__main__':
s = gen_test('abc')
print(list(s))
iter_index = dir(s).index('__iter__')
next_index = dir(s).index('__next__')
print(dir(s)[iter_index])
print(dir(s)[next_index])
看看结果;
没问题,它依然是个生成器 。这里我们看成2个单词来理解,yield,from。第一个动作是生成,第二个动作是从的意思。
合在一起,我们可以理解为从哪里产生结果。
yield是直接生成,而yield from则是从子生成器里生成。因为它具有迭代的性质,如果我们还是可以通过迭代的方式来输出。
这里的从哪里生成我们就可以理解为:
def gen_from(*args):
for items in args:
for item in items:
yield item
这里我们只是简化成了如下
def gen(*args):
for item in args:
yield from item