定义生成器
在python中,生成器就是一种数据类型,它自动实现了迭代器协议,生成器有两种表现形式:生成器表达式和生成器函数。
生成器表达式:元组解析式
>>> tu = (x**2 for x in range(5)) # 元组解析式定义的生成器 >>> tu 生成器对象定义时,并没有内容,只有在开始执行后才会通过执行__next__取值 <generator object <genexpr> at 0x10bc1da98> >>> list(tu) [0, 1, 4, 9, 16] >>> tu2 = (x*2 for x in range(3)) # 生成器对象就是迭代器 >>> tu2.__next__() # 生成器对象有__next__属性,就是迭代器 0 >>> tu2.__next__() 2 >>> tu2.__next__() 4 >>> tu2.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
生成器函数:定义生成器函数必须使用yield关键字。在python中,yield关键字是生成器的标志。
>>> def g(): # 使用关键字yield定义生成器 ... yield 1 ... yield 2 ... yield 3 ... >>> ge = g() # 执行函数,产生生成器对象 >>> ge <generator object g at 0x10bc1d9a8> # 生成器对象 >>> ge.__next__() # 生成器对象也有__next__属性,生成器也是迭代器 1 >>> ge.__next__() 2 >>> ge.__next__() 3 >>> ge.__next__() # 抛出__next__的异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
含有yield关键字的函数是一个生成器对象,这个生成器对象也是迭代器。生成器是一种用普通函数语法定义的迭代器。
yield语句的作用就是在调用的时候返回相应的值。详解上面的运行过程:
1.ge = g():ge引用生成器对象;
2.ge.__next__():由__next__方法触发生成器的执行,遇到了第一个yield语句,将值返回,并暂停执行(挂起);
3.ge.__next__():由__next__方法触发生成器的执行,从上次暂停的位置开始,继续向下执行,遇到yield语句,将值返回,又暂停,而不是从头开始执行;
4.ge.__next__():重复上面的操作;
ge.__next__():从上面的暂停位置开始,继续向下执行,但后面没有可执行的对象了,于是__next__()发出StopIteration异常。
yield除了作为生成器函数的标志之外,还有一个功能:返回值。
生成器对象定义时,并没有内容,只有在开始执行后才会通过执行__next__()方法在生成器中取值,并且生成器是一次性的,只能被取值一次。
>>> t = [1, 2, 3, 4, 5] >>> t1 = (i for i in t) # 通过t产生生成器t1,此时t1只是声明的生成器对象,没有执行其内容 >>> t2 = (j for j in t1) # 通过t1产生生成器t2,此时t2只是声明的生成器对象,产生它的t1并未被执行 >>> list(t1) # 将t1转换为列表,此时t1第一次执行,且只能取值一次 [1, 2, 3, 4, 5] >>> list(t2) # t1已被取值,执行t2时再从t1中取不到值了 []
yield
yield在函数中具有返回值的功能,它和return的区别是,return将值返回后,函数结束,若return语句后面还有语句,则return语句后面的内容不再执行。而yield将值返回后,函数暂停执行,并记住当前的位置,等待下一次的继续执行。总的来说,普通函数止于return,生成器函数,执行时遇到yield则挂起。
>>> def r_yield(n): # 定义生成器函数 ... print('start...') ... while n > 0: ... print('before yield') ... yield n # yield生成器标志并返回值 ... n -= 1 ... print('after yield') ... >>> r = r_yield(3) # 引用生成器对象,此时没有执行函数体内语句 >>> for i in r: # for循环,调用__next__依次取值 ... print(i) ... start... before yield 3 # 遇到yield,返回值并暂停 after yield # 从上次暂停的位置开始继续执行 before yield 2 # 再次遇到yield,返回值并暂停 after yield # 重复上面的过程 before yield 1 after yield
send
在生成器中,触发生成器执行的除了有生成器中魔术方法的__next__方法和内建函数next()之外,还有一个生成器的自带普通方法:send()方法。
send方法的作用有两个:
1.触发生成器,使生成器从当前位置继续向下执行,等同于__next__方法;
2.赋值的作用,给当前挂起的位置的yield赋值,yield的作用除了返回值之外(相当于return),还可以赋值给变量,而send可以将它的参数传给当前挂起位置的yield,再由yield赋值给函数内的局部变量。
>>> def test(): ... print('开始执行啦') ... print('执行第一次') ... yield 1 ... print('执行第二次') ... second = yield 2 # 此处yield既返回值,也可给变量second赋值 ... print('send传过来的值:', second) ... print('执行第三次') ... yield 3 ... print('执行结束啦') ... >>> t = test() >>> t.__next__() # __next__方法触发生成器 开始执行啦 执行第一次 1 >>> next(t) # next()函数触发生成器 执行第二次 2 >>> t.send('send传值') # send方法触发生成器,并将参数传给当前挂起位置的yield,由yield赋变量second send传过来的值: send传值 # send既触发了下一次的执行,又承接了上一次的内容即传值 执行第三次 3