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语句处继续执行。