2
2
3
3
#生成器
4
4
5
- 生成器(英文:generator)是一个非常迷人的东西,也常被认为是python的高级编程技能。不过,我依然很乐意在这里跟读者——尽管你可能是一个初学者——探讨这个话题,因为我相信读者看本教程的目的,绝非仅仅将自己限制于初学者水平,一定有一颗不羁的心——要成为python高手。那么,开始了解生成器吧。
5
+ 上节中,我们曾经做过这样的操作:
6
6
7
- 还记得上节的“迭代器”吗?生成器和迭代器有着一定的渊源关系。生成器必须是可迭代的,诚然它又不仅仅是迭代器,但除此之外,又没有太多的别的用途,所以,我们可以把它理解为非常方便的自定义迭代器。
8
-
9
- 最这个关系实在感觉有点糊涂了。稍安勿躁,继续阅读即明了。
10
-
11
- ##简单的生成器
12
-
13
- >>> my_generator = (x*x for x in range(4))
14
-
15
- 这是不是跟列表解析很类似呢?仔细观察,它不是列表,如果这样的得到的才是列表:
16
-
17
- >>> my_list = [x*x for x in range(4)]
18
-
19
- 以上两的区别在于是` [] ` 还是` () ` ,虽然是细小的差别,但是结果完全不一样。
20
-
21
- >>> dir(my_generator)
22
- ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__',
23
- '__iter__',
24
- '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running',
25
- 'next',
26
- 'send', 'throw']
27
-
28
- 为了容易观察,我将上述结果进行了重新排版。是不是发现了在迭代器中必有的方法` __iter__() ` 和` next() ` ,这说明它是迭代器。如果是迭代器,就可以用for循环来依次读出其值。
29
-
30
- >>> for i in my_generator:
31
- ... print i
32
- ...
33
- 0
34
- 1
35
- 4
36
- 9
37
- >>> for i in my_generator:
38
- ... print i
39
- ...
40
-
41
- 当第一遍循环的时候,将my_generator里面的值依次读出并打印,但是,当再读一次的时候,就发现没有任何结果。这种特性也正是迭代器所具有的。
42
-
43
- 如果对那个列表,就不一样了:
44
-
45
- >>> for i in my_list:
46
- ... print i
47
- ...
48
- 0
49
- 1
50
- 4
51
- 9
52
- >>> for i in my_list:
53
- ... print i
54
- ...
55
- 0
56
- 1
57
- 4
58
- 9
59
-
60
- 难道生成器就是把列表解析中的` [] ` 换成` () ` 就行了吗?这仅仅是生成器的一种表现形式和使用方法罢了,仿照列表解析式的命名,可以称之为“生成器解析式”(或者:生成器推导式、生成器表达式)。
61
-
62
- 生成器解析式是有很多用途的,在不少地方替代列表,是一个不错的选择。特别是针对大量值的时候,如上节所说的,列表占内存较多,迭代器(生成器是迭代器)的优势就在于少占内存,因此无需将生成器(或者说是迭代器)实例化为一个列表,直接对其进行操作,方显示出其迭代的优势。比如:
7
+ >>> my_tup = (x**x for x in range(4))
8
+ >>> my_tup
9
+ <generator object <genexpr> at 0x02B7C2B0>
10
+
11
+ generator,翻译过来是生成器。
63
12
64
- >>> sum(i*i for i in range(10))
65
- 285
13
+ 生成器是一个非常迷人的东西,也常被认为是Python的高级编程技能。不过,我依然很乐意在这里跟读者——尽管你可能是一个初学者——探讨这个话题,因为我相信读者看本教程的目的,绝非仅仅将自己限制于初学者水平,一定有一颗不羁的心——要成为Python高手。那么,开始了解生成器吧。
66
14
67
- 请读者注意观察上面的 ` sum() ` 运算,不要以为里面少了一个括号,就是这么写。是不是很迷人?如果列表,你不得不:
15
+ 既然在探讨“迭代器”的时候出现了生成器,这就说明生成器和迭代器有着一定的渊源关系。
68
16
69
- >>> sum([i*i for i in range(10)])
70
- 285
17
+ 没错!生成器必须是可迭代的,它首先是迭代器。
71
18
72
- 通过生成器解析式得到的生成器,掩盖了生成器的一些细节,并且适用领域也有限。下面就要剖析生成器的内部,深入理解这个魔法工具 。
19
+ 但,它毕竟还是生成器,具有生成器的特质 。
73
20
74
- ##定义和执行过程
21
+ ##定义生成器
75
22
76
- yield这个词在汉语中有“生产、出产”之意,在python中 ,它作为一个关键词(你在变量、函数、类的名称中就不能用这个了) ,是生成器的标志。
23
+ 定义生成器,必须使用 ` yield ` 关键词。 yield这个词在汉语中有“生产、出产”之意,在Python中 ,它作为一个关键词,是生成器的标志。
77
24
78
25
>>> def g():
79
26
... yield 0
80
27
... yield 1
81
28
... yield 2
82
- ...
29
+
83
30
>>> g
84
31
<function g at 0xb71f3b8c>
85
32
86
- 建立了一个非常简单的函数,跟以往看到的函数唯一不同的地方是用了三个yield语句。然后进行下面的操作 :
33
+ 建立了一个非常简单的函数,里面有 ` yield ` 发起的三个语句。下面看如何使用它 :
87
34
88
35
>>> ge = g()
89
36
>>> ge
90
37
<generator object g at 0xb7200edc>
91
38
>>> type(ge)
92
39
<type 'generator'>
93
40
94
- 上面建立的函数返回值是一个生成器(generator)类型的对象。
41
+ 调用函数,得到了一个生成器(generator)对象。
42
+
43
+ Python 2:
95
44
96
45
>>> dir(ge)
97
46
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
98
47
99
- 在这里看到了 ` __iter__() ` 和 ` next() ` ,说明它是迭代器。既然如此,当然可以:
48
+ Python 3:
100
49
101
- >>> ge.next()
50
+ >>> dir(ge)
51
+ ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
52
+
53
+ 在这里看到了` __iter__() ` 和` next() ` 或` __next__() ` ,虽然我们在函数体内并没有显示地写出` __iter__() ` 、` next() ` 和` __next__() ` ,仅仅写了` yield ` 语句,它就已经成为迭代器了。
54
+
55
+ 既然如此,当然可以:
56
+
57
+ >>> ge.next() #Python 3: ge.__next__(),下同,从略
102
58
0
103
59
>>> ge.next()
104
60
1
@@ -109,14 +65,18 @@ yield这个词在汉语中有“生产、出产”之意,在python中,它作
109
65
File "<stdin>", line 1, in <module>
110
66
StopIteration
111
67
112
- 从这个简单例子中可以看出,那个含有yield关键词的函数返回值是一个生成器类型的对象,这个生成器对象就是迭代器。
68
+ 从这个简单例子中可以看出,那个含有` yield ` 关键词的函数是一个生成器对象,这个生成器对象也是迭代器。
69
+
70
+ 于是可以这样定义:把含有` yield ` 语句的函数称作生成器。
113
71
114
- 我们把含有yield语句的函数称作生成器。 生成器是一种用普通函数语法定义的迭代器。通过上面的例子可以看出,这个生成器(也是迭代器),在定义过程中并没有像上节迭代器那样写 ` __iter__() ` 和 ` next() ` ,而是只要用了yield语句,那个普通函数就神奇般地成为了生成器,也就具备了迭代器的功能特性 。
72
+ 生成器是一种用普通函数语法定义的迭代器。
115
73
116
- yield语句的作用,就是在调用的时候返回相应的值。详细剖析一下上面的运行过程:
74
+ 通过上面的例子可以看出,这个生成器(也是迭代器),在定义过程中并没有像上节迭代器那样写 ` __iter__() ` 和 ` next() ` ,而是只要用了yield语句,那个普通函数就神奇般地成为了生成器,也就具备了迭代器的功能特性。
117
75
118
- 1 . ` ge = g() ` :除了返回生成器之外,什么也没有操作,任何值也没有被返回。
119
- 2 . ` ge.next() ` :直到这时候,生成器才开始执行,遇到了第一个yield语句,将值返回,并暂停执行(有的称之为挂起)。
76
+ ` yield ` 语句的作用,就是在调用的时候返回相应的值。详细剖析一下上面的运行过程:
77
+
78
+ 1 . ` ge = g() ` :返回生成器之外。
79
+ 2 . ` ge.next() ` :生成器才开始执行,遇到了第一个yield语句,将值返回,并暂停执行(有的称之为挂起)。
120
80
3 . ` ge.next() ` :从上次暂停的位置开始,继续向下执行,遇到yield语句,将值返回,又暂停。
121
81
4 . ` gen.next() ` :重复上面的操作。
122
82
5 . ` gene.next() ` :从上面的挂起位置开始,但是后面没有可执行的了,于是` next() ` 发出异常。
@@ -125,10 +85,12 @@ yield语句的作用,就是在调用的时候返回相应的值。详细剖析
125
85
126
86
##yield
127
87
128
- 为了弄清楚yield和return的区别,我们写两个没有什么用途的函数:
88
+ 函数返回值,本来已经有了一个` return ` ,现在又出现了` yield ` ,这两个有什么区别?
89
+
90
+ 为了区别,我们写两个没有什么用途的函数:
129
91
130
92
>>> def r_return(n):
131
- ... print "You taked me."
93
+ ... print "You taked me." #Python 3: print("You taked me."),下同,从略
132
94
... while n > 0:
133
95
... print "before return"
134
96
... return n
@@ -141,20 +103,20 @@ yield语句的作用,就是在调用的时候返回相应的值。详细剖析
141
103
>>> rr
142
104
3
143
105
144
- 从函数被调用的过程可以清晰看出,` rr = r_return(3) ` ,函数体内的语句就开始执行了,遇到return ,将值返回,然后就结束函数体内的执行。所以return后面的语句根本没有执行。这是return的特点 ,关于此特点的详细说明请阅读[ 《函数(2)》中的返回值相关内容] ( ./202 ) 。
106
+ 从函数被调用的过程可以清晰看出,` rr = r_return(3) ` ,函数体内的语句就开始执行了,遇到 ` return ` ,将值返回,然后就结束函数体内的执行。所以 ` return ` 后面的语句根本没有执行。这是 ` return ` 的特点 ,关于此特点的详细说明请阅读[ 《函数(2)》中的返回值相关内容] ( ./202.md ) 。
145
107
146
108
下面将return改为yield:
147
109
148
110
>>> def y_yield(n):
149
- ... print "You taked me."
111
+ ... print "You taked me." #Python 3: print("You taked me."),下同,从略
150
112
... while n > 0:
151
113
... print "before yield"
152
114
... yield n
153
115
... n -= 1
154
116
... print "after yield"
155
117
...
156
118
>>> yy = y_yield(3) #没有执行函数体内语句
157
- >>> yy.next() #开始执行
119
+ >>> yy.next() #Python 3: yy.__next__(),下同,从略
158
120
You taked me.
159
121
before yield
160
122
3 #遇到yield,返回值,并暂停
@@ -172,11 +134,11 @@ yield语句的作用,就是在调用的时候返回相应的值。详细剖析
172
134
File "<stdin>", line 1, in <module>
173
135
StopIteration
174
136
175
- 结合注释和前面对执行过程的分析,读者一定能理解yield的特点了,也深知与return的区别了 。
137
+ 结合注释和前面对执行过程的分析,读者一定能理解 ` yield ` 的特点了,也深知与 ` return ` 的区别了 。
176
138
177
- 一般的函数,都是止于return 。作为生成器的函数,由于有了yield ,则会遇到它挂起,如果还有return,遇到它就直接抛出SoptIteration异常而中止迭代 。
139
+ 一般的函数,都是止于 ` return ` 。作为生成器的函数,由于有了 ` yield ` ,则会遇到它挂起。
178
140
179
- 斐波那契数列已经是老相识了。不论是循环、迭代都用它举例过,现在让我们还用它吧,只不过是要用上yield:
141
+ 斐波那契数列已经是老相识了。不论是循环、迭代都用它举例过,现在让我们还用它吧,只不过是要用上 ` yield ` 。
180
142
181
143
#!/usr/bin/env python
182
144
# coding=utf-8
@@ -194,60 +156,16 @@ yield语句的作用,就是在调用的时候返回相应的值。详细剖析
194
156
if __name__ == "__main__":
195
157
f = fibs(10)
196
158
for i in f:
197
- print i ,
159
+ print i , #Python 3: print(i, end=',')
198
160
199
161
运行结果如下:
200
162
201
163
$ python 21501.py
202
164
1 1 2 3 5 8 13 21 34 55
203
165
204
- 用生成器方式实现的斐波那契数列是不是跟以前的有所不同了呢?读者可以将本教程中已经演示过的斐波那契数列实现方式做一下对比,体会各种方法的差异。
205
-
206
- 经过上面的各种例子,已经明确,一个函数中,只要包含了yield语句,它就是生成器,也是迭代器。这种方式显然比前面写迭代器的类要简便多了。但,并不意味着上节的就被抛弃。是生成器还是迭代器,都是根据具体的使用情景而定。
207
-
208
- ##生成器方法
209
-
210
- 在python2.5以后,生成器有了一个新特征,就是在开始运行后能够为生成器提供新的值。这就好似生成器和“外界”之间进行数据交流。
211
-
212
- >>> def repeater(n):
213
- ... while True:
214
- ... n = (yield n)
215
- ...
216
- >>> r = repeater(4)
217
- >>> r.next()
218
- 4
219
- >>> r.send("hello")
220
- 'hello'
221
-
222
- 当执行到` r.next() ` 的时候,生成器开始执行,在内部遇到了` yield n ` 挂起。注意在生成器函数中,` n = (yield n) ` 中的` yield n ` 是一个表达式,并将结果赋值给n,虽然不严格要求它必须用圆括号包裹,但是一般情况都这么做,请读者也追随这个习惯。
223
-
224
- 当执行` r.send("hello") ` 的时候,原来已经被挂起的生成器(函数)又被唤醒,开始执行` n = (yield n) ` ,也就是将send()方法发送的值返回。这就是在运行后能够为生成器提供值的含义。
225
-
226
- 如果接下来再执行` r.next() ` 会怎样?
227
-
228
- >>> r.next()
229
-
230
- 什么也没有,其实就是返回了None。按照前面的叙述,读者可以看到,这次执行` r.next() ` ,由于没有传入任何值,yield返回的就只能是None.
231
-
232
- 还要注意,send()方法必须在生成器运行后并挂起才能使用,也就是yield至少被执行一次。如果不是这样:
233
-
234
- >>> s = repeater(5)
235
- >>> s.send("how")
236
- Traceback (most recent call last):
237
- File "<stdin>", line 1, in <module>
238
- TypeError: can't send non-None value to a just-started generator
239
-
240
- 就报错了。但是,可将参数设为None:
241
-
242
- >>> s.send(None)
243
- 5
244
-
245
- 这时返回的是调用函数的时传入的值。
246
-
247
- 此外,还有两个方法:close()和throw()
166
+ 用生成器方式实现的斐波那契数列是不是跟以前的有所不同了呢?读者可以将本书中已经演示过的斐波那契数列实现方式做一下对比,体会各种方法的差异。
248
167
249
- - throw(type, value=None, traceback=None):用于在生成器内部(生成器的当前挂起处,或未启动时在定义处)抛出一个异常(在yield表达式中)。
250
- - close():调用时不用参数,用于关闭生成器。
168
+ 经过上面的各种例子,已经明确,一个函数中,只要包含了` yield ` 语句,它就是生成器,也是迭代器。这种方式显然比前面写迭代器的类要简便多了。但,并不意味着上节的就被抛弃。是生成器还是迭代器,都是根据具体的使用情景而定。
251
169
252
170
最后一句,你在编程中,不用生成器也可以。
253
171
0 commit comments