知行编程网知行编程网  2022-12-11 20:30 知行编程网 隐藏边栏  3 
文章评分 0 次,平均分 0.0
导语: 本文主要介绍了关于深入理解Python中的生成器的相关知识,包括python生成器作用,以及生成器python原理这些编程知识,希望对大家有参考作用。

深入理解Python中的生成器

有了列表推导,我们可以直接创建一个列表。但是,由于内存限制,列表容量肯定是有限的。并且,创建一个包含 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循环随之结束。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

知行编程网
知行编程网 关注:1    粉丝:1
这个人很懒,什么都没写
扫一扫二维码分享