4.md 6.1 KB
Newer Older
1 2 3 4
# 四、生成器 #

## 1、为什么需要生成器 ##

T
TwoWater 已提交
5
通过上面的学习,可以知道列表生成式,我们可以直接创建一个列表。
6

T
TwoWater 已提交
7 8 9 10 11 12 13
但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含 1000 万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

**所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?**

这样就不必创建完整的 list,从而节省大量的空间。

**在 Python 中,这种一边循环一边计算的机制,称为生成器:generator。**
14 15 16 17 18

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

T
TwoWater 已提交
19
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值。并在下一次执行 next()方法时从当前位置继续运行。
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

那么如何创建一个生成器呢?


## 2、生成器的创建 ##

最简单最简单的方法就是把一个列表生成式的 `[]` 改成 `()`

```python
# -*- coding: UTF-8 -*-
gen= (x * x for x in range(10))
print(gen)
```

输出的结果:

```txt
<generator object <genexpr> at 0x0000000002734A40>
```

T
TwoWater 已提交
40 41 42 43 44
创建 List 和 generator 的区别仅在于最外层的 `[]``()`

但是生成器并不真正创建数字列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目“产生” ( yield ) 出来。

生成器表达式使用了“惰性计算” ( lazy evaluation,也有翻译为“延迟求值”,我以为这种按需调用 call by need 的方式翻译为惰性更好一些),只有在检索时才被赋值( evaluated ),所以在列表比较长的情况下使用内存上更有效。
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67


那么竟然知道了如何创建一个生成器,那么怎么查看里面的元素呢?

## 3、遍历生成器的元素 ##

按我们的思维,遍历用 for 循环,对了,我们可以试试:

```python
# -*- coding: UTF-8 -*-
gen= (x * x for x in range(10))

for num  in  gen :
	print(num)
```

没错,直接这样就可以遍历出来了。当然,上面也提到了迭代器,那么用 next() 可以遍历吗?当然也是可以的。


## 4、以函数的形式实现生成器 ##

上面也提到,创建生成器最简单最简单的方法就是把一个列表生成式的 `[]` 改成 `()`。为啥突然来个以函数的形式来创建呢?

T
TwoWater 已提交
68 69 70 71 72
其实生成器也是一种迭代器,但是你只能对其迭代一次。

这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。你通过遍历来使用它们,要么用一个“for”循环,要么将它们传递给任意可以进行迭代的函数和结构。

而且实际运用中,大多数的生成器都是通过函数来实现的。那么我们该如何通过函数来创建呢?
73 74 75 76 77 78 79 80 81

先不急,来看下这个例子:

```python
# -*- coding: UTF-8 -*-
def my_function():
    for i in range(10):
        print ( i )

3
347073565@qq.com 已提交
82
my_function()
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
```

输出的结果:

```txt
0
1
2
3
4
5
6
7
8
9
```

如果我们需要把它变成生成器,我们只需要把 `print ( i )` 改为 `yield i` 就可以了,具体看下修改后的例子:

```python
# -*- coding: UTF-8 -*-
def my_function():
    for i in range(10):
        yield i

3
347073565@qq.com 已提交
108
print(my_function())
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
```

输出的结果:

```txt
<generator object my_function at 0x0000000002534A40>
```

但是,这个例子非常不适合使用生成器,发挥不出生成器的特点,生成器的最好的应用应该是:你不想同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环。因为这样会耗很大的资源。

比如下面是一个计算斐波那契数列的生成器:

```python
# -*- coding: UTF-8 -*-
def fibon(n):
    a = b = 1
    for i in range(n):
        yield a
        a, b = b, a + b

# 引用函数
for x in fibon(1000000):
    print(x , end = ' ')
```

运行的效果:

T
TwoWater 已提交
136
![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-07-%E8%AE%A1%E7%AE%97%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%94%9F%E6%88%90%E5%99%A8.gif)
137

T
TwoWater 已提交
138
你看,运行一个这么大的参数,也不会说有卡死的状态,因为这种方式不会使用太大的资源。这里,最难理解的就是 generator 和函数的执行流程不一样。函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。而变成 generator 的函数,在每次调用 next() 的时候执行,遇到 yield语句返回,再次执行时从上次返回的 yield 语句处继续执行。
139 140 141 142 143 144 145 146 147 148 149 150 151 152

比如这个例子:

```python
# -*- coding: UTF-8 -*-
def odd():
    print ( 'step 1' )
    yield ( 1 )
    print ( 'step 2' )
    yield ( 3 )
    print ( 'step 3' )
    yield ( 5 )

o = odd()
3
347073565@qq.com 已提交
153 154 155
print( next( o ) )
print( next( o ) )
print( next( o ) )
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
```

输出的结果:

```txt
step 1
1
step 2
3
step 3
5
```

可以看到,odd 不是普通函数,而是 generator,在执行过程中,遇到 yield 就中断,下次又继续执行。执行 3 次 yield 后,已经没有 yield 可以执行了,如果你继续打印 `print( next( o ) ) ` ,就会报错的。所以通常在 generator 函数中都要对错误进行捕获。

## 5、打印杨辉三角 ##

通过学习了生成器,我们可以直接利用生成器的知识点来打印杨辉三角:

```python
# -*- coding: UTF-8 -*-
def triangles( n ):         # 杨辉三角形
    L = [1]
    while True:
        yield L
        L.append(0)
        L = [ L [ i -1 ] + L [ i ] for i in range (len(L))]

n= 0
for t in triangles( 10 ):   # 直接修改函数名即可运行
    print(t)
    n = n + 1
    if n == 10:
        break
```

输出的结果为:

```txt
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
```
T
TwoWater 已提交
206 207