有了列表推导,我们可以直接创建一个列表。但是,由于内存限制,列表容量肯定是有限的。并且,创建一个包含 100 万个元素的列
表格不仅占用大量的存储空间,而且如果我们只需要访问前几个元素,后面的大部分元素占用的空间都会被浪费掉。
那么,如果列表元素可以按照一定的算法计算出来,我们是不是可以在循环中不断计算后面的元素呢?由于不必创建完整列表,因此可以节省大量空间。在 Python 中,这种 while 循环计算的机制称为生成器。
创建生成器的方法有很多种。第一种方法很简单,只要把一个列表生成的[]改成(),就创建了一个生成器:
>>> L = [x * x for x in range(10)]>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>>
g = (x * x for x in range(10))>>> g
<generator object <genexpr> at 0x104feab40>
创建L和g的区别只是最外层的[]和(),L是列表,g是生成器。
我们可以直接打印出列表的每个元素,但是我们如何打印出生成器的每个元素呢?
如果要一个一个打印出来,可以通过generator的next()方法:
>>> g.next()
0
>>> g.next()
1
>>> g.next()
4
>>> g.next()
9
>>> g.next()
16
>>> g.next()
25
>>> g.next()
36
>>> g.next()
49
>>> g.next()
64
>>> g.next()
81
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
正如我们所说,生成器保存了算法。每次调用 next() 时,都会计算下一个元素的值,直到计算完最后一个元素。当没有更多元素时,将抛出 StopIteration 错误。
当然,上面这种不断调用next()的方法也太变态了。正确的做法是使用for循环,因为生成器也是一个可迭代对象:
>>> g = (x * x for x in range(10))
>>> for n in g:
... print n
...
0
1
4
9
16
25
36
49
64
81
所以,我们创建生成器后,基本上不会调用 next() 方法,而是通过 for 循环对其进行迭代。
发电机非常强大。如果计算算法比较复杂,不能用类似列表生成的for循环实现,也可以用函数实现。
比如著名的斐波那契数列(Fibonacci)中,除了第一个和第二个数外,任何数都可以通过前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波那契数列不能用列表理解来写,但很容易用函数打印出来:
def fib(max):
n, a, b = 0, 0, 1
while n < max: print b
a, b = b, a + b
n = n + 1
上面的函数可以输出斐波那契数列的前N个数:
>>> fib(6)
1
1
2
3
5
8
仔细观察可以看出,fib函数实际上定义了斐波那契数列的计算规则。它可以从第一个元素开始计算任何后续元素。这个逻辑其实和生成器很像。
也就是说,上面的函数和生成器只差一步之遥。要将 fib 函数变成生成器,只需将 print b 更改为 yield b:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
这是定义生成器的另一种方式。如果一个函数定义中包含了 yield 关键字,那么这个函数就不再是一个普通的函数,而是一个生成器:
>>> fib(6)
<generator object fib at 0x104feaaa0>
这里,最难理解的是generator和function的执行过程是不一样的。函数是顺序执行的,遇到return语句或者函数语句的最后一行就返回。成为generator的函数在每次调用next()时执行,遇到yield语句就返回,再次执行时从上次返回的yield语句继续执行。
举个简单的例子,定义一个generator,依次返回数字1,3,5:
>>> def odd():
... print 'step 1'
... yield 1
... print 'step 2'
... yield 3
... print 'step 3'
... yield 5
...
>>> o = odd()
>>> o.next()
step 1
1
>>> o.next()
step 2
3
>>> o.next()
step 3
5
>>> o.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
可以看出odd并不是一个普通的函数,而是一个生成器。执行过程中遇到yield会中断,下次继续执行。执行了3次yield后,没有yield可执行,所以第四次调用next()会报错。
回到 fib 的例子,如果我们在循环中一直调用 yield,我们会一直被中断。当然,循环必须要设置一个条件才能退出循环,否则会生成一个数字列表。
同样,将函数改成generator后,我们基本上不再用next()调用,而是直接用for循环迭代:
>>> for n in fib(6):
... print n
...
1
1
2
3
5
8
小结
生成器是一个非常强大的工具。在Python中可以简单的把列表生成改成生成器,也可以通过函数实现复杂的逻辑生成器。
理解生成器的工作原理,就是在for循环中不断计算下一个元素,在合适的条件下结束for循环。对于由函数变来的生成器,当遇到return语句或者执行到函数体的最后一行时,就是结束生成器的指令,for循环随之结束。
本文为原创文章,版权归知行编程网所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Python SymPy 找到极值09/26
- ♥ 如何在python中将华氏温度转换为摄氏温度?08/23
- ♥ python检查文件是否存在08/14
- ♥ python如何使用实例作为属性11/20
- ♥ python amd64的含义09/30
- ♥ 如何在python中使用dict函数?12/11
内容反馈