Python编程技术:Python生成器详解generator和yield

Python编程技术:Python生成器详解generator和yield

什么是生成器?

通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator

生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。

生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器

生成器generator和yield基础

generator使用场景:
  1. 当我们需要一个公用的,按需生成的数据
  2.某个事情执行一部分,另一部分在某个事件发生后再执行下一部分,实现异步。
注意事项:
  1. yield from generator_obj 本质上类似于 for item in generator_obj: yield item
  2.generator函数中允许使用return,但是return 后不允许有返回值

generator有以下特点:

  • 遵循迭代器(iterator)协议,迭代器协议需要实现__iter__、next接口
  • 能过多次进入、多次返回,能够暂停函数体中代码的执行

在python的函数(function)定义中,只要出现了yield表达式(Yield expression),那么事实上定义的是一个generator function, 调用这个generator function返回值是一个generator。这根普通的函数调用有所区别。

def gen_generator():
    yield 1
 
def gen_value():
    return 1
    
ret = gen_generator()
print (ret, type(ret))    #<generator object gen_generator at 0x02645648> <type 'generator'>
ret = gen_value()
print (ret, type(ret))    # 1 <type 'int'>

从上面的代码可以看出,gen_generator函数返回的是一个generator实例.

通过以下代码理解generator和yield和使用方法:

def gen_example():
    print ('before any yield')
    yield 'first yield'
    print ('between yields')
    yield 'second yield'
    print ('no yield anymore')

gen = gen_example()
gen.next()    # 第一次调用next
#before any yield
#'first yield'
 
gen.next()    # 第二次调用next
#between yields
#'second yield'
 
gen.next()    # 第三次调用next
#no yield anymore
#Traceback (most recent call last):
#File "<stdin>", line 1, in <module>
#StopIteratio

调用gen example方法并没有输出任何内容,说明函数体的代码尚未开始执行。当调用generator的next方法,generator会执行到yield 表达式处,返回yield表达式的内容,然后暂停(挂起)在这个地方,所以第一次调用next打印第一句并返回“first yield”。 暂停意味着方法的局部变量,指针信息,运行环境都保存起来,直到下一次调用next方法恢复。第二次调用next之后就暂停在最后一个yield,再次调用next()方法,则会抛出StopIteration异常。

因为for语句能自动捕获StopIteration异常,所以generator(本质上是任何iterator)较为常用的方法是在循环中使用:

def generator_example():
    yield 1
    yield 2
 
for e in generator_example():
    print (e)

generator function产生的generator与普通的function区别
  (1)function每次都是从第一行开始运行,而generator从上一次yield开始的地方运行
  (2)function调用一次返回一个(一组)值,而generator可以多次返回
  (3)function可以被无数次重复调用,而一个generator实例在yield最后一个值 或者return之后就不能继续调用了

generator创建

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

>>> mylist = [ x for x in range(1, 10)] 
>>> mylist 
[1, 2, 3, 4, 5, 6, 7, 8, 9] 
>>> gen = (x for x in range(1,10)) 
>>> gen 
<generator object <genexpr> at 0x7f1d7fd0f5a0>
>>> gen.next() 
1 
>>> gen.next() 
2 
>>> gen.next() 
3 
... 
>>> gen.next() 
9 
>>> gen.next() 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
StopIteration

其实我们可以使用for循环来代替next()方式, 这样才更符合高效的编程思路:

>>> gen = ( x for x in range(1, 10)) 
>>> for num in gen: 
...     print num 

第二种方法是前面演示过的,跟yield配合使用。
我们以斐波拉契数列为例:
著名的斐波拉契数列(1,1,2,3,5,8,13,21……除了第一个和第二个数外,任意一个数都是由其前两个数相加的和)

def fib(max):
	n,a,b = 0,0,1
	while n < max:
		print (b)
		a,b = b,a+b#相当于将一个tuple(b,a+b)赋值给a,b
		n = n + 1
	return
fib(6)结果:1 1 2 3 5 8

改成generator方式:其实,上述fib()和generator非常相近了。只需要把print(b)变成yield b 就可以了

def fib(max):
	n,a,b = 0,0,1
	while n < max:
		yield b 
		a,b = b,a+b#相当于将一个tuple(b,a+b)赋值给a,b
		n = n + 1
	return

m = fib(6)
for i in m :
	print(i)
fib(6)结果:1 1 2 3 5 8

这就是定义generator的第二种方法。如果一个函数中包含yield关键字,那么这个函数就不再是普通函数,而是一个generator。两者的执行流程可以这么区别:普通函数是顺序执行,遇到return或者最后一行代码函数就会返回。而generator,在每次调用next()的时候执行,遇到yield语句返回。再次执行的时候,从上次返回的yield语句处继续执行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章