Skip to content

Commit 5753cb8

Browse files
committed
iterator
1 parent 6627e5a commit 5753cb8

5 files changed

Lines changed: 375 additions & 47 deletions

File tree

204.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ filter、map、reduce、lambda、yield
106106

107107
有了它们,最大的好处是程序更简洁;没有它们,程序也可以用别的方式实现,只不过麻烦一些罢了。所以,还是能用则用之吧。更何况,恰当地使用这几个函数,能让别人感觉你更牛X。
108108

109+
(注:本节不对yield进行介绍,请阅读[《生成器》](./215.md)
110+
109111
##lambda
110112

111113
lambda函数,是一个只用一行就能解决问题的函数,听着是多么诱人呀。看下面的例子:

214.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,49 @@
125125
$ python 21402.py
126126
[0, 1, 1, 2, 3, 5]
127127

128-
现在给读者一个思考问题:要在斐波那契数列中找出大于1000的最小的数,能不能在上述代码基础上改造得出呢?
128+
>给读者一个思考问题:要在斐波那契数列中找出大于1000的最小的数,能不能在上述代码基础上改造得出呢?
129+
130+
关于列表和迭代器之间的区别,还有两个非常典型的内建函数:`range()``xrange()`,研究一下这两个的差异,会有所收获的。
131+
132+
range(...)
133+
range(stop) -> list of integers
134+
range(start, stop[, step]) -> list of integers
135+
136+
>>> dir(range)
137+
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
138+
139+
`range()`的帮助文档和方法中可以看出,它的结果是一个列表。但是,如果用`help(xrange)`查看:
140+
141+
class xrange(object)
142+
| xrange(stop) -> xrange object
143+
| xrange(start, stop[, step]) -> xrange object
144+
|
145+
| Like range(), but instead of returning a list, returns an object that
146+
| generates the numbers in the range on demand. For looping, this is
147+
| slightly faster than range() and more memory efficient.
148+
149+
`xrange()`返回的是对象,并且进一步告诉我们,类似`range()`,但不是列表。在循环的时候,它跟`range()`相比“slightly faster than range() and more memory efficient”,稍快并更高的内存效率(就是省内存呀)。查看它的方法:
150+
151+
>>> dir(xrange)
152+
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__len__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
153+
154+
看到令人兴奋的`__iter__`了吗?说明它是可迭代的,它返回的是一个可迭代的对象。
155+
156+
也就是说,通过`range()`得到的列表,会一次性被读入内存,而`xrange()`返回的对象,则是需要一个数值才从返回一个数值。比如这样一个应用:
157+
158+
还记得`zip()`吗?
159+
160+
>>> a = ["name", "age"]
161+
>>> b = ["qiwsir", 40]
162+
>>> zip(a,b)
163+
[('name', 'qiwsir'), ('age', 40)]
164+
165+
如果两个列表的个数不一样,就会以短的为准了,比如:
166+
167+
>>> zip(range(4), xrange(100000000))
168+
[(0, 0), (1, 1), (2, 2), (3, 3)]
169+
170+
第一个`range(4)`产生的列表被读入内存;第二个是不是也太长了?但是不用担心,它根本不会产生那么长的列表,因为只需要前4个数值,它就提供前四个数值。如果你要修改为`range(100000000)`,就要花费时间了,可以尝试一下哦。
129171

130172
迭代器的确有迷人之处,但是它也不是万能之物。比如迭代器不能回退,只能如过河的卒子,不断向前。另外,迭代器也不适合在多线程环境中对可变集合使用(这句话可能理解有困难,先混个脸熟吧,等你遇到多线程问题再说)。
131173

215.md

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
>圣灵所结的果子,就是仁爱、喜乐、和平、忍耐、恩慈、良善、信实、温柔、节制。这样的事,没有律法禁止。凡属基督耶稣的人,是已经把肉体连肉体的邪情私欲同钉在十字架上了。我们若是靠圣灵得生,就当靠圣灵行事。不要贪图虚名,彼此惹气,互相嫉妒。(GALATIANS 5:22-26)
2+
3+
#生成器
4+
5+
生成器(英文:generator)是一个非常迷人的东西,也常被认为是python的高级编程技能。不过,我依然很乐意在这里跟读者——尽管你可能是一个初学者——探讨这个话题,因为我相信读者看本教程的目的,绝非仅仅将自己限制于初学者水平,一定有一颗不羁的心——要成为python高手。那么,开始了解生成器吧。
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+
为了容易观察,我将上述结果进行了重新排版。是不是发现了在迭代器中必有的方法`__inter__()``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+
生成器解析式是有很多用途的,在不少地方替代列表,是一个不错的选择。特别是针对大量值的时候,如上节所说的,列表占内存较多,迭代器(生成器是迭代器)的优势就在于少占内存,因此无需将生成器(或者说是迭代器)实例化为一个列表,直接对其进行操作,方显示出其迭代的优势。比如:
63+
64+
>>> sum(i*i for i in range(10))
65+
285
66+
67+
请读者注意观察上面的`sum()`运算,不要以为里面少了一个括号,就是这么写。是不是很迷人?如果列表,你不得不:
68+
69+
>>> sum([i*i for i in range(10)])
70+
285
71+
72+
通过生成器解析式得到的生成器,掩盖了生成器的一些细节,并且适用领域也有限。下面就要剖析生成器的内部,深入理解这个魔法工具。
73+
74+
##定义和执行过程
75+
76+
yield这个词在汉语中有“生产、出产”之意,在python中,它作为一个关键词(你在变量、函数、类的名称中就不能用这个了),是生成器的标志。
77+
78+
>>> def g():
79+
... yield 0
80+
... yield 1
81+
... yield 2
82+
...
83+
>>> g
84+
<function g at 0xb71f3b8c>
85+
86+
建立了一个非常简单的函数,跟以往看到的函数唯一不同的地方是用了三个yield语句。然后进行下面的操作:
87+
88+
>>> ge = g()
89+
>>> ge
90+
<generator object g at 0xb7200edc>
91+
>>> type(ge)
92+
<type 'generator'>
93+
94+
上面建立的函数返回值是一个生成器(generator)类型的对象。
95+
96+
>>> dir(ge)
97+
['__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+
99+
在这里看到了`__iter__()``next()`,说明它是迭代器。既然如此,当然可以:
100+
101+
>>> ge.next()
102+
0
103+
>>> ge.next()
104+
1
105+
>>> ge.next()
106+
2
107+
>>> ge.next()
108+
Traceback (most recent call last):
109+
File "<stdin>", line 1, in <module>
110+
StopIteration
111+
112+
从这个简单例子中可以看出,那个含有yield关键词的函数返回值是一个生成器类型的对象,这个生成器对象就是迭代器。
113+
114+
我们把含有yield语句的函数称作生成器。生成器是一种用普通函数语法定义的迭代器。通过上面的例子可以看出,这个生成器(也是迭代器),在定义过程中并没有像上节迭代器那样写`__inter__()``next()`,而是只要用了yield语句,那个普通函数就神奇般地成为了生成器,也就具备了迭代器的功能特性。
115+
116+
yield语句的作用,就是在调用的时候返回相应的值。详细剖析一下上面的运行过程:
117+
118+
1. `ge = g()`:除了返回生成器之外,什么也没有操作,任何值也没有被返回。
119+
2. `ge.next()`:直到这时候,生成器才开始执行,遇到了第一个yield语句,将值返回,并暂停执行(有的称之为挂起)。
120+
3. `ge.next()`:从上次暂停的位置开始,继续向下执行,遇到yield语句,将值返回,又暂停。
121+
4. `gen.next()`:重复上面的操作。
122+
5. `gene.next()`:从上面的挂起位置开始,但是后面没有可执行的了,于是`next()`发出异常。
123+
124+
从上面的执行过程中,发现yield除了作为生成器的标志之外,还有一个功能就是返回值。那么它跟return这个返回值有什么区别呢?
125+
126+
##yield
127+
128+
为了弄清楚yield和return的区别,我们写两个没有什么用途的函数:
129+
130+
>>> def r_return(n):
131+
... print "You taked me."
132+
... while n > 0:
133+
... print "before return"
134+
... return n
135+
... n -= 1
136+
... print "after return"
137+
...
138+
>>> rr = r_return(3)
139+
You taked me.
140+
before return
141+
>>> rr
142+
3
143+
144+
从函数被调用的过程可以清晰看出,`rr = r_return(3)`,函数体内的语句就开始执行了,遇到return,将值返回,然后就结束函数体内的执行。所以return后面的语句根本没有执行。这是return的特点,关于此特点的详细说明请阅读[《函数(2)》中的返回值相关内容](./202)
145+
146+
下面将return改为yield:
147+
148+
>>> def y_yield(n):
149+
... print "You taked me."
150+
... while n > 0:
151+
... print "before yield"
152+
... yield n
153+
... n -= 1
154+
... print "after yield"
155+
...
156+
>>> yy = y_yield(3) #没有执行函数体内语句
157+
>>> yy.next() #开始执行
158+
You taked me.
159+
before yield
160+
3 #遇到yield,返回值,并暂停
161+
>>> yy.next() #从上次暂停位置开始继续执行
162+
after yield
163+
before yield
164+
2 #又遇到yield,返回值,并暂停
165+
>>> yy.next() #重复上述过程
166+
after yield
167+
before yield
168+
1
169+
>>> yy.next()
170+
after yield #没有满足条件的值,抛出异常
171+
Traceback (most recent call last):
172+
File "<stdin>", line 1, in <module>
173+
StopIteration
174+
175+
结合注释和前面对执行过程的分析,读者一定能理解yield的特点了,也深知与return的区别了。
176+
177+
一般的函数,都是止于return。作为生成器的函数,由于有了yield,则会遇到它挂起,如果还有return,遇到它就直接抛出SoptIteration异常而中止迭代。
178+
179+
斐波那契数列已经是老相识了。不论是循环、迭代都用它举例过,现在让我们还用它吧,只不过是要用上yield:
180+
181+
#!/usr/bin/env python
182+
# coding=utf-8
183+
184+
def fibs(max):
185+
"""
186+
斐波那契数列的生成器
187+
"""
188+
n, a, b = 0, 0, 1
189+
while n < max:
190+
yield b
191+
a, b = b, a + b
192+
n = n + 1
193+
194+
if __name__ == "__main__":
195+
f = fibs(10)
196+
for i in f:
197+
print i ,
198+
199+
运行结果如下:
200+
201+
$ python 21501.py
202+
1 1 2 3 5 8 13 21 34 55
203+
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()
248+
249+
- throw(type, value=None, traceback=None):用于在生成器内部(生成器的当前挂起处,或未启动时在定义处)抛出一个异常(在yield表达式中)。
250+
- close():调用时不用参数,用于关闭生成器。
251+
252+
------
253+
254+
[总目录](./index.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[上节:迭代器](./214.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[下节:异常](./216.md)
255+
256+
如果你认为有必要打赏我,请通过支付宝:**qiwsir@126.com**,不胜感激。
257+
258+
259+
260+

2code/21501.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
4+
def fibs(max):
5+
n, a, b = 0, 0, 1
6+
while n < max:
7+
yield b
8+
a, b = b, a + b
9+
n = n + 1
10+
11+
if __name__ == "__main__":
12+
f = fibs(10)
13+
for i in f:
14+
print i ,

0 commit comments

Comments
 (0)