「Python」Generator生成器
产生需求
任何一个功能的出现,必定是有其对应的需求的。
使用Python获取斐波拉契数列,斐波拉契数列(Fibonacci):除第一个数以外,所有的数字都是由前面两项相加得到。
def fib(n):
"""
获取斐波拉契数列
:param n: 需要获取自然数n前的所有斐波拉契数列中的数字
:return: result,以列表的形式返回数列
"""
a, b = 0, 1
result = [a, b]
while True:
c = a + b
if c <= n:
result.append(c)
a, b = b, c
else:
break
return result
a = fib(100) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
问题
通过上面的函数我们可以获取数字n
前面的所有斐波拉契数列,但是如果我使用函数fib()
获取了100前的所有斐波拉契数列,但是我再使用的过程中却发现我只需要10以前的斐波拉契数列就行了。
这个时候,就产生了内存浪费,制造了100前的所有斐波拉契数列,但是你实际需求只用了10以前的。
抛出疑问
能不能写出这样一个函数,就是我所产生的斐波拉契数列个数是小于等于100前的,但是至于我用到哪些,现在是还不确定的,但是我可以随用随造,不用10以后的斐波拉契数列的话,那么这个函数就不会制造后面的呢?
解决办法
这个时候,就用到了生成器。
结合列表推导式,把其两端的[]
换成()
即可得到生成器,一个简单的举例:
a = (i for i in range(3))
与列表推导式不同的是,列表生成器在创建的时候直接生成了一个列表:[0, 1, 2]
,而生成器则是创建了一个生成器对象,只有在后续调用的时候才会生成其中的元素。
b = [i for i in range(3)]
print(a) # <generator object <genexpr> at 0x000001A7767A5690>
print(b) # [0, 1, 2]
如何使用生成器呢?生成器每次使用都需要进行调用,且生成的值只能使用一次,之后就产生下一个元素:
print(a.__next__()) # 0
print(a.__next__()) # 1
print(next(a)) # 2
print(a.__next__()) # 报错:StopIteration
如果超出生成器中元素个数,就会报错。
上述逐个调用生成器中的元素的话,如果超出则会报错,但是使用for
循环的话就不会报错:
for i in a:
print(i) #0, 1, 2
其实Python中很多地方都有生成器的使用场景,例如使用open()
函数对文件执行逐行读取的时候,就是每次读取一行,不读就不取。
yield关键字
回到最初提出的问题,如何实现函数中也实现生成器的效果呢?这个时候就需要使用yield
关键字。
def fib(n):
a, b = 0, 1
i = 0
while True:
i += 1
print(f'开始第{i}次循环...')
c = a + b
if c <= n:
yield c
a, b = b, c
else:
break
print(f'第{i}次循环结束...')
# 正常函数在创建的时候就会运行完毕,但是加入yield关键字的函数,在创建的时候,运行到yield关键字的位置就会停止,并在第一次调用.__next__()方法的时候将yield关键字后面的变量返回出来,然后继续执行后续代码,直至再次遇见yield。
a = fib(100) # [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
while True:
try:
print(a.__next__())
except Exception as e:
print('已打印完所有元素...', e)
break