一.什么是生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
生成器的作用: 用简单的方式来完成迭代
要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )
>>> a = [x*2 for x in range(10)]
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> b = (x*2 for x in range(10))
>>> next(b)
0
>>> next(b)
2
>>>
二.怎样创建生成器
在一个一般函数中使用yield
关键字,可以实现一个最简单的生成器,此时这个函数变成一个生成器函数。yield
与return
返回相同的值,区别在于return
返回后,函数状态终止,而yield
会保存当前函数的执行状态,在返回后,函数又回到之前保存的状态继续执行。
生成器保存的是算法,每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。当然,这种不断调用 next() 实在是太变态了,正确的方法是使用 for 循环,因为生成器也是可迭代对象。所以,我们创建了一个生成器后,基本上永远不会调用 next() ,而是通过 for 循环来迭代它,并且不需要关心 StopIteration 异常。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
def creatNum():
a,b = 0,1
for i in range(s):
print(b)
a,b = b,a+b
creatNum()
将上段代码改成生成器, 将 print 改成 yeild
def creatNum():
a,b = 0,1
for i in range(s):
yeild b
a,b = b,a+b
creatNum()
三.生成器函数与一般函数的不同
- 生成器函数包含一个或者多个
yield
- 当调用生成器函数时,函数将返回一个对象,但是不会立刻向下执行
- 像
__iter__()
和__next__()
方法等是自动实现的,所以我们可以通过next()
方法对对象进行迭代 - 一旦函数被
yield
,函数会暂停,控制权返回调用者 - 局部变量和它们的状态会被保存,直到下一次调用
- 函数终止的时候,
StopIteraion
会被自动抛出
举例:
四.生成器的方法
1.next()方法
执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)
使用 next 函数
In [10]: def gen():
....: i = 0
....: while i<5:
....: temp = yield i
....: print(temp)
....: i+=1
....:
In [11]: f = gen()
In [12]: next(f)
Out[12]: 0
In [13]: next(f)
None
Out[13]: 1
In [14]: next(f)
None
Out[14]: 2
In [15]: next(f)
None
Out[15]: 3
In [16]: next(f)
None
Out[16]: 4
In [17]: next(f)
None
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-17-468f0afdf1b9> in <module>()
----> 1 next(f)
StopIteration:
使用__next__()
方法
In [18]: f = gen()
In [19]: f.__next__()
Out[19]: 0
In [20]: f.__next__()
None
Out[20]: 1
In [21]: f.__next__()
None
Out[21]: 2
In [22]: f.__next__()
None
Out[22]: 3
In [23]: f.__next__()
None
Out[23]: 4
In [24]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-24-39ec527346a9> in <module>()
----> 1 f.__next__()
StopIteration:
2.send()方法
这是我认为生成器最重要的功能,我们可以通过send()方法,向生成器内部传递参数
In [43]: f = gen()
In [44]: f.__next__()
Out[44]: 0
In [45]: f.send('haha')
haha
Out[45]: 1
In [46]: f.__next__()
None
Out[46]: 2
In [47]: f.send('haha')
haha
Out[47]: 3
In [48]:
def count(n):
x = 0
while x < n:
value = yield x
if value is not None:
print 'Received value: %s' %value
x += 1
还是之前的count函数,唯一的区别是我们将”yield x”的值赋给了变量value,并将其打印出来。如何给value传值呢?
gen = count(5)
print gen.next() # print 0
print gen.send('Hello') # Received value: Hello, then print 1
注意这里第一次不能直接调用 gen.send('hello'),要先调用下 next()或者 send(None)
我们先调用next()方法,让代码执行到yield关键字(这步必须要),当前打印出0。然后当我们调用”gen.send(‘Hello’)”时,字符串’Hello’就被传入生成器中,并作为yield关键字的执行结果赋给变量”value”,所以控制台会打印出”Received value: Hello”。然后代码继续执行,直到下一次遇到yield关键字后暂定,此时生成器返回的是1。
简单的说,send()就是next()的功能,加上传值给yield。如果你有兴趣看下Python的源码,你会发现,其实next()的实现,就是send(None)。
3.close()方法
顾名思义,close()方法就是关闭生成器。生成器被关闭后,再次调用next()方法,不管能否遇到yield关键字,都会立即抛出StopIteration异常。
gen = (x for x in range(5))
gen.close()
gen.next() # StopIteration
4.throw()方法
除了向生成器函数内部传递参数,我们还可以传递异常
def throw_gen():
try:
yield 'Normal'
except ValueError:
yield 'Error'
finally:
print 'Finally'
gen = throw_gen()
print gen.next() # Normal
print gen.next() # Finally, then StopIteration
如果像往常一样调用next()方法,会返回’Normal’。再次调用next(),会进入finally语句,打印’Finally’,同时由于函数退出,生成器会抛出StopIteration异常。我们换个方式,在第一次调用next()方法后,调用throw()方法,情况会怎样?
gen = throw_gen()
print gen.next() # Normal
print gen.throw(ValueError) # Error
print gen.next() # Finally, then StopIteration
我们会看到,throw()方法向生成器函数内部传递了”ValueError”异常,代码进入”except ValueError”语句,当遇到下一个yield时才暂停并退出,此时生成器返回的是’Error’字符串。简单的说,throw()就是next()的功能,加上传异常给yield。
五.生成器的应用--多任务
def test1():
while True:
print("---1---")
yield None
def test2():
while True:
print("---2---")
yield None
t1 = test1()
t2 = test2()
while True:
t1.__next__()
t2.__next__()
总结
生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
- 节约内存
- 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的