龙空技术网

Python学习笔记(二):生成器

BugBear 184

前言:

眼前我们对“js 生成器函数”大体比较注意,咱们都想要剖析一些“js 生成器函数”的相关知识。那么小编也在网摘上汇集了一些有关“js 生成器函数””的相关文章,希望姐妹们能喜欢,我们一起来学习一下吧!

Hello!大家好,我是BugBear,一个专注于分享软件测试干货的测试开发。今天我们来学习一下Python的生成器。

一、生成器定义

在Python中,一边循环一边计算的机制,称为生成器:generator

二、生成器作用

列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间

我又想要得到庞大的数据,又想让它占用空间少,那就用生成器

三、生成器创建

生成器的创建有两种方式:

第一种方法:只要把一个列表生成式的[]改成(),就创建了一个generator

# 列表生成式转为生成器print((i for i in range(9)))<generator object <genexpr> at 0x10fc137d0>

第二种方法:如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。调用函数就是创建了一个生成器(generator)对象

data_list = [i for i in range(9)]def test():    k=0    while True:        if k <= len(data_list):            j = yield 5 # yield必须用在函数中            print(j+k)            k+=1        else:            breakprint(test())<generator object test at 0x1070b7750>

四、生成器工作原理

第一、生成器(generator)通过重复调用next()方法,直到捕获一个异常

第二、带有 yield 的函数不再是一个普通函数,而是一个生成器generator。可用next()调用生成器对象来取值。next 两种方式 t.__next__() | next(t)。 可用for 循环获取返回值(每执行一次,取生成器里面一个值)(基本上不会用next()来获取下一个返回值,而是直接使用for循环来迭代)

第三、yield相当于 return 返回一个值,并且记住这个返回的位置,下次迭代时,代码从yield的下一条语句开始执行,第一次遇到yield时,返回的会是yield右边的值

data_list = [i for i in range(9)]def test():    k=0    while True:        if k <= len(data_list):            j = yield 5 # yield必须用在函数中            print(j+k)            k+=1        else:            breaka = test()print('第一次:{}'.format(a.__next__())) # 5print('第二次:{}'.format(a.__next__())) # exception报错

当运行到yield时,第一次直接返回右边的值5给调用者a,并记录返回的位置,后面的代码则不会运行,下次迭代时,会直接从print(j+k)开始运行,但是此时j是没有赋值的,是一个NoneType,所以j+k就引发报错

Traceback (most recent call last):  File "/Users/Desktop/demo/learn/rt001/20210121/demo001.py", line 24, in <module>    print('第二次:{}'.format(a.__next__()))  File "/Users/Desktop/demo/learn/rt001/20210121/demo001.py", line 16, in test    print(j+k)TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'第一次:5

第四、.send() 和next()一样,都能让生成器继续往下走一步(下次遇到yield停),但send()能传一个值,这个值作为yield表达式整体的结果。换句话说,就是send可以强行修改上一个yield表达式值。

data_list = [i for i in range(9)]def test():    k=0    while True:        if k <= len(data_list):            j = yield 5 # yield必须用在函数中            print(j+k)            k+=1        else:            breaka = test()print('第一次:{}'.format(a.__next__()))print('第二次:{}'.format(a.send(10)))print('第三次:{}'.format(a.send(10)))# 结果第一次:510第二次:511第三次:5

我们针对上面实例进行分析:

可以看到test函数中有一个 j=yield 5,第一次迭代到这里会返回5给调用者也就是a,j还没有赋值则为NoneType,此时记录返回的代码位置。

第二次迭代时,使用a.send(10),则从之前记录的代码位置的下一行代码开始运行,也就是print(j+k)。强行修改yield 5表达式的值为10并赋值给了j,那么j =10。如果不是用a.send()而使用a.__next__()的话,那么则会因为j为NoneType引发报错。

下面我们来看看官方的解释吧

generator.__next__() 开始一个生成器函数的执行或是从上次执行的 yield 表达式位置恢复执行。 当一个生成器函数通过 __next__() 方法恢复执行时,当前的 yield 表达式总是取值为 None。 随后会继续执行到下一个 yield 表达式,其 expression_list 的值会返回给 __next__() 的调用者。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。

此方法通常是隐式地调用,例如通过 for 循环或是内置的 next() 函数。

generator.send(value) 恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。 send() 方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。 当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。

通过实例解析:

1、当运行a.__next__()时,遇到yield 5时,直接return 5,则打印 第一次: 5

2、当运行a.send(10)时,会将之前yield 5表达式的结果直接变为10,并赋值给j,然后从print(j+k)开始执行代码,直到遇到下一次的yield,此时打印 10 和 第二次: 5,后面以此类推

五、yield使用

yield相关我们常用的有next,send,__next__,close等

next和__next__的作用就是按需取下一个值close就是销毁了这个生成器send的作用是给yield的左边传参

当然第一次调用的时候,因为yield的执行顺序是先右边再左边,所以,我们遇到yield就停止了运行,此时send是无法给左边赋值的,那我们第一次调用只能send(None)

def gen_test():    value = 0    while True:        s = yield value        print('s=', s)        value += 1  m = gen_test()m.send(None) # 第一次启动生成器时需要给Nonefor i in range(10):    m.send(i)

结果如下

s= 0s= 1s= 2s= 3s= 4s= 5s= 6s= 7s= 8s= 9

这里我们每次有个变量s,它是用来接收send传值的。

至于为什么第一次使用None进行传值,是因为第一次我们遇到yield就会暂停。而并没有进行赋值操作。所以我们传None

OK,看输出结果。确实是在第二次send的时候我们才能接收到send的值,而第一次必须传None,我们从他的祖先去看看

Sending a value into a new generator produces a TypeError:>>> f().send("foo")Traceback (most recent call last):...TypeError: can't send non-None value to a just-started generator

OK,看了下它的底层,确实是必须在一个生成器第一次迭代的时候必须传None

六、yield from用法

yield from简单实现

yield是每次“惰性返回”一个值,其实从名字中就能看出,yield from 是yield的升级改进版本,如果将yield理解成“返回”,那么yield from就是“从什么(生成器)里面返回”,这就构成了yield from的一般语法,即 yield from generator

从上面可以看出,yield from 后面可以跟的可以是生成器 、元组、 列表、range()函数产生的序列等可迭代对象

简单地说,yield from generator 。实际上就是返回另外一个生成器。而yield只是返回一个元素。

从这个层面来说,有下面的等价关系:

yield from iterable# 本质上等于for item in iterbale:	yield item

yield是直接生成,而yield from则是从子生成器里生成。因为它具有迭代的性质,如果我们还是可以通过迭代的方式来输出

def gen_from(*args):    for items in args:        for item in items:            yield item

简化为

def gen_from(*args):	for item in args:		yield from item

那我们找一个简单的实例来看看吧

data = []data_list = [x for x in range(9)]data.append(data_list)def test(data):    for i in data:        yield from i # 从data_list中迭代返回valuea = test(data)print(a.__next__())print(a.__next__())print(a.__next__())

结果为

012

标签: #js 生成器函数