生成器与斐波拉契数列
生成器
通过表达式构造生成器
通过列表生成式,我们可以用简单的一行代码生成列表
print([i for i in range(10)])
print(type([i for i in range(10)]))
输出结果为:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<class 'list'>
如果我们把列表生成式中[]
符号改写成()
,生成的就不再是列表,而是生成器(generator)。
print((i for i in range(10)))
print(type((i for i in range(10))))
输出结果为:
<generator object <genexpr> at 0x0000013760BDF228>
<class 'generator'>
可以看出,当我们print一个生成器的时候,python并没有输出每个元素,而是产生了一个<generator object <genexpr> at 0x0000013760BDF228>
。这个输出告诉你了这是一个生成器并告诉了它的地址。
获取生成器的值
通过next()函数
然而我们如果想要获取每个元素的值呢,那么我们就必须要调用next()
函数,next()
函数可以把生成器的每个元素逐个返回出来。如果我们要打印我们刚才定义的生成器,那么我们就要调用10次next()
函数。需要注意的是next()
函数只是返回元素,就好像return
一样,所以在编译环境里还需要用print
进行打印。
在调用next()
函数前,我们首先要实例化一个生成器。
g = (i for i in range(10))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
输出结果为
0
1
2
3
4
5
6
7
8
9
从以上代码中可以看到,我们一共调用了10次next()
函数,通过print()
把每个生成器的元素都给打印了出来。如果我们调用了第11次next()
函数的话,就会出现报错StopIteration
。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
因此通过next()
函数来获取生成器的值是很繁琐的。因此我们一般通过for循环来或许元素。
通过for循环
g = (i for i in range(10))
for i in g:
print(i)
输出结果为:
0
1
2
3
4
5
6
7
8
9
这样看是不是简单了很多了。事实上生成器就是一个迭代器,for循环可以用于任何可迭代的对象。
生成器与列表的区别
列表存储着一系列元素,如果元素非常的多,那么就会占用大量的内存。
生成器相比于列表,其实更像是一个规则,它可以根据某种设定好的规则一个个推算出下一个元素的值。因为它不需要占用大量的内存空间。
上述的生成器的例子比较简单,我们可以通过函数制定一种更加复杂规则。例如我们要推算一个自然数的集合,自然数是无穷的,所以用列表是无法存储的。然而用生成器的话,我们只要制定一个种规则,如从0开始每个元素是前一个元素的值加1,通过While
和next()
我相信是可以一直把自然数打出来的。(数学上把自然数定义为可数数,其实就是用一种规则可数,但还是无穷的。)
通过函数构造生成器和斐波拉契数列
自然数的例子很简单。我们通过一个较为复杂的斐波拉契数列进行说明。的斐波拉契数列的规则就是通过计算前两个数的和获取第三个数,以此无限类推下去。
首先我们构造一个函数并调用它输出前6个值。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
fib(6)
print(type(fib))
# print(type(fib(6)))
输出结果
1
1
2
3
5
8
<class 'function'>
这里定义的是一个fib
是一个函数,而调用了fib(6)
以后我们return
是一个str
类型,其实就是"done"
。
这还不是一个生成器,如果我们要把它改成一个生成器拿,其实只要把print(b)
改成yield b
就可以了。yield
函数类似一个return
,会返回一个值,此处返回的就是b
的值,它会在被next()
函数调用时返回,可以通过print(next(f))
打印出来。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print("before yield")
yield b
print("after yield")
a, b = b, a + b
n = n + 1
return 'done'
fib(6)
print(type(fib))
print(type(fib(6)))
输出为:
<class 'function'>
<class 'generator'>
为了更好得说明,我在yield b
前后分别加了两个打印。
从上面的代码看出,fib(6)
的调用并没有任何输出。从type(fib(6))
中也可以看出fib(6)
已经是一个生成器了,而不是函数的调用了。
需要注意的是,fib
仍然是一个函数。
因为如果我们还想获取其中的元素,就需要通过next()
函数了。同样的,我们先实例化一个生成器。
f = fib(6)
print(next(f))
输出结果为:
before yield
1
从结果可以看出,我们调用了一次next(f)
函数,fib
函数并没有打印"after yield"
,也就说只执行到yield b
,此时返回的b
通过print
打印出来是1
。
让我们再多调用几次next(f)
函数。
f = fib(6)
print(next(f))
print(next(f))
print(next(f))
输出为:
before yield
1
after yield
before yield
1
after yield
before yield
2
可以看出,执行的效果是这样的,第一次调用next(f)
,函数执行到yield b
停止,第二次调用next(f)
,函数从第一次yield b
停止的地方继续开始执行,由于while
循环的原因,循环执行到yield b
再次停止,就这样周而复始执行下去。
如果这样子还不清楚,可以看下面这个廖雪峰大大python上的例子。直接上图。安利一下。
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317799226173f45ce40636141b6abc8424e12b5fb27000