python学习之---生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含1000万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)

要创建一个generator,有很多种方法。

第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

1. 简单的列表操作:

1 >>> list1 = [x*x for x in range(10)]
2 >>> list1
3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
4 >>> list2 = [2*x for x in range(12)]
5 >>> list2
6 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
7 >>> 

2. 生成器操作:

1 >>> list3 = (x*x for x in range(10))
2 >>> list3
3 <generator object <genexpr> at 0x7f302fdb6cd0>
4 >>> list1
5 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
6 >>> list2
7 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
8 >>> 

如代码所示,list1和list2是一个列表,而list3是一个generator,在任意时刻我们可以随便打印出list1和list2的列表元素,但是我们怎么才可以打印出list3的元素呢?,在这里generator提供一个打印自身元素的函数,next()

 1 >>> list3
 2 <generator object <genexpr> at 0x7f302fdb6cd0>
 3 >>> list3.next()
 4 0
 5 >>> list3.next()
 6 1
 7 >>> list3.next()
 8 4
 9 >>> list3.next()
10 9
11 >>> list3.next()
12 16
13 >>> list3.next()
14 25
15 >>> list3.next()
16 36
17 >>> list3.next()
18 49
19 >>> list3.next()
20 64
21 >>> list3.next()
22 81
23 >>> list3.next()
24 Traceback (most recent call last):
25   File "<stdin>", line 1, in <module>
26 StopIteration
27 >>> 

generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

使用next方法虽然可以计算出生成器的值,但是,实在是太不人性化了,在此,还有没有更好的方法来输出生成器的值呢?方法肯定是有的,那就是采用for迭代输出:

 1 >>> list4 = (x*x for x in range(10))
 2 >>> list3
 3 <generator object <genexpr> at 0x7f302fdb6cd0>
 4 >>> for n in list4:
 5 ...     print n
 6 ... 
 7 0
 8 1
 9 4
10 9
11 16
12 25
13 36
14 49
15 64
16 81
17 >>> 

所以,我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

 1 >>> 
 2 >>> def fibla(max):
 3 ...     n,a,b = 0,0,1
 4 ...     while n < max:
 5 ...         print b
 6 ...         a,b = b,a+b
 7 ...         n = n + 1
 8 ... 
 9 >>> fibla(8)
10 1
11 1
12 2
13 3
14 5
15 8
16 13
17 21
18 >>> 

观察之后发现,fibla函数其实是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print b改为yield b就可以了:

 1 >>> def fibla(max):
 2 ...     n,a,b = 0,0,1
 3 ...     while n < max:
 4 ...         print b
 5 ...         a,b = b,a+b
 6 ...         n = n + 1
 7 ... 
 8 >>> fibla(8)
 9 1
10 1
11 2
12 3
13 5
14 8
15 13
16 21
17 #上下对比
18 >>> def fibla(max):
19 ...     n,a,b = 0,0,1
20 ...     while n < max:
21 ...         yield b
22 ...         a,b = b,a+b
23 ...         n = n +1
24 ... 
25 >>> fibla(8)
26 <generator object fibla at 0x7f302fd5ca00>
27 >>> 

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

 1 >>> def fibla(max):
 2 ...     n,a,b = 0,0,1
 3 ...     while n < max:
 4 ...         yield b
 5 ...         a,b = b,a+b
 6 ...         n = n +1
 7 ... 
 8 >>> fibla(8)
 9 <generator object fibla at 0x7f302fd5ca00>
10 >>> for x in fibla(8):
11 ...     print x
12 ... 
13 1
14 1
15 2
16 3
17 5
18 8
19 13
20 21
21 >>> 
原文地址:https://www.cnblogs.com/blogofwyl/p/4294616.html