Skip to content

Commit 95ca802

Browse files
committed
p3
1 parent ce10318 commit 95ca802

File tree

5 files changed

+213
-73
lines changed

5 files changed

+213
-73
lines changed

214.md

Lines changed: 159 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,80 @@
66

77
迭代,对于读者已经不陌生了,曾有专门一节来讲述,如果印象不深,请复习[《迭代》](./128.md)
88

9-
正如读者已知,对序列(列表、元组)、字典和文件都可以用`iter()`方法生成迭代对象,然后用`next()`方法访问。当然,这种访问不是自动的,如果用for循环,就可以自动完成上述访问了。
9+
>>> hasattr(list, '__iter__')
10+
True
1011

11-
如果用`dir(list)`,`dir(tuple)`,`dir(file)`,`dir(dict)`来查看不同类型对象的属性,会发现它们都有一个名为`__iter__`的东西。这个应该引起读者的关注,因为它和迭代器(iterator)、内置的函数iter()在名字上是一样的,除了前后的双下划线。望文生义,我们也能猜出它肯定是跟迭代有关的东西。当然,这种猜测也不是没有根据的,其重要根据就是英文单词,如果它们之间没有一点关系,肯定不会将命名搞得一样
12+
不仅仅是列表,文件、字典都有一个名为`__iter__`的方法,这说明它们都是可迭代的
1213

13-
猜对了。`__iter__`就是对象的一个特殊方法,它是迭代规则(iterator potocol)的基础。或者说,对象如果没有它,就不能返回迭代器,就没有`next()`方法,就不能迭代
14+
`__iter__()`是对象的一个特殊方法,它是迭代规则(iterator potocol)的基础,有了它,说明对象是可迭代的
1415

15-
>提醒注意,如果读者用的是python3.x,迭代器对象实现的是`__next__()`方法,不是`next()`。并且,在python3.x中有一个内建函数next(),可以实现`next(it)`,访问迭代器,这相当于于python2.x中的`it.next()`(it是迭代对象)。
16+
跟迭代有关的一个内建函数`iter()`,它的文档中这样描述:
1617

17-
那些类型是list、tuple、file、dict对象有`__iter__()`方法,标着他们能够迭代。这些类型都是python中固有的,我们能不能自己写一个对象,让它能够迭代呢?
18+
>>> help(iter)
19+
Help on built-in function iter in module __builtin__:
1820

19-
当然呢!要不然python怎么强悍呢。
21+
iter(...)
22+
iter(collection) -> iterator
23+
iter(callable, sentinel) -> iterator
24+
25+
Get an iterator from an object. In the first form, the argument must
26+
supply its own iterator, or be a sequence.
27+
In the second form, the callable is called until it returns the sentinel.
28+
29+
这个函数前文介绍过,它返回一个迭代器对象。比如:
30+
31+
>>> lst = [1, 2, 3, 4]
32+
>>> iter_lst = iter(lst)
33+
>>> iter_lst
34+
<listiterator object at 0x02BE8D50> #Python 3返回结果:<list_iterator object at 0x00000000034CD6D8>
35+
36+
从返回结果中可以看出,`iter_lst`引用的是迭代器对象。那么,`iter_lst``lst`有区别吗?
37+
38+
>>> hasattr(lst, "__iter__")
39+
True
40+
>>> hasattr(iter_lst, "__iter__")
41+
True
42+
43+
它们都有`__iter__`,这是相同点,说明它们都是可迭代的。
44+
45+
但是:
46+
47+
Python 2:
48+
49+
>>> hasattr(lst, "next")
50+
False
51+
>>> hasattr(iter_lst, "next")
52+
True
53+
54+
Python 3:
55+
56+
>>> hasattr(lst, "__next__")
57+
False
58+
>>> hasattr(iter_lst, "__next__")
59+
True
60+
61+
这就是两者的区别。我们像`iter_lst`所引用的对象那样,具有`next()`(Python 2)或者`__next__()`(Python 3)方法的对象,称之为迭代器对象。显见,迭代器对象必然是可迭代的,反之则不然。
62+
63+
Python 3中迭代器对象实现的是`__next__()`方法,不是`next()`。并且,在Python 3中有一个内建函数`next()`,可以实现`next(it)`访问迭代器,这相当于于Python 2中的`it.next()`(it是迭代对象)。
64+
65+
为了体现Python强悍,自己写一个迭代器对象。
2066

2167
#!/usr/bin/env python
2268
# coding=utf-8
2369

2470
"""
2571
the interator as range()
2672
"""
27-
class MyRange(object):
73+
class MyRange(object): #Python 3: class MyRange:
2874
def __init__(self, n):
29-
self.i = 0
75+
self.i = 1
3076
self.n = n
3177

3278
def __iter__(self):
3379
return self
3480

35-
def next(self):
36-
if self.i < self.n:
81+
def next(self): #Python 3: def __next__(self):
82+
if self.i <= self.n:
3783
i = self.i
3884
self.i += 1
3985
return i
@@ -42,65 +88,71 @@
4288

4389
if __name__ == "__main__":
4490
x = MyRange(7)
45-
print "x.next()==>", x.next()
46-
print "x.next()==>", x.next()
47-
print "------for loop--------"
48-
for i in x:
49-
print i
91+
print [i for i in x] #Python 3中使用print()函数,下同,从略
5092

5193
将代码保存,并运行,结果是:
5294

53-
$ python 21401.py
54-
x.next()==> 0
55-
x.next()==> 1
56-
------for loop--------
57-
2
58-
3
59-
4
60-
5
61-
6
95+
[1, 2, 3, 4, 5, 6, 7]
6296

63-
以上代码的含义,是自己仿写了拥有`range()`的对象,这个对象是可迭代的。分析如下
97+
以上代码的含义,是自己仿写了类似`range()`的类,但是跟`range()`又有所不同,除了结果不同之外,还有
6498

65-
类MyRange的初始化方法`__init__()`就不用赘述了,因为前面已经非常详细分析了这个方法,如果复习,请阅读[《类(2)》](./207md)相关内容。
99+
-`MyRange`的初始化方法`__init__()`就不用赘述了,因为前面已经非常详细分析了这个方法,如果复习,请阅读[《类(2)》](./207md)相关内容。
66100

67-
`__iter__()`是类中的核心,它返回了迭代器本身。一个实现了`__iter__()`方法的对象,即意味着其实可迭代的
101+
- `__iter__()`是类中的核心,它返回了迭代器本身。一个实现了`__iter__()`方法的对象,即意味着它是可迭代的
68102

69-
含有`next()`的对象,就是迭代器,并且在这个方法中,在没有元素的时候要发起`StopIteration()`异常。
103+
- 实现`next()`或者`__next__()`方法,从而使得这个对象是迭代器对象,并且方法中判断,在不满足条件的时候要发起`StopIteration()`异常。
70104

71-
如果对以上类的调用换一种方式
105+
再来看`range()`(以下仅仅限于Python 2)
72106

73-
if __name__ == "__main__":
74-
x = MyRange(7)
75-
print list(x)
76-
print "x.next()==>", x.next()
107+
>>> a = range(7)
108+
>>> hasattr(a, "__iter__")
109+
True
110+
>>> hasattr(a, "next")
111+
False
112+
>>> print a
113+
[0, 1, 2, 3, 4, 5, 6]
77114

78-
运行后会出现如下结果:
115+
所以我写的类和`range()`还是有很大区别的。
116+
117+
为了能深入理解迭代器的工作过程,我们这样来操作:
118+
119+
if __name__ == "__main__":
120+
x = MyRange(3)
121+
print "self.n=",x.n,";","self.i=",x.i #Python 3中使用print()函数,下同,从略
122+
x.next()
123+
print "self.n=",x.n,";","self.i=",x.i
124+
x.next()
125+
print "self.n=",x.n,";","self.i=",x.i
126+
x.next()
127+
print "self.n=",x.n,";","self.i=",x.i
128+
x.next()
129+
print "self.n=",x.n,";","self.i=",x.i
130+
131+
运行结果如下:
132+
133+
self.n= 3 ; self.i= 1
134+
self.n= 3 ; self.i= 2
135+
self.n= 3 ; self.i= 3
136+
self.n= 3 ; self.i= 4
79137

80-
$ python 21401.py
81-
[0, 1, 2, 3, 4, 5, 6]
82-
x.next()==>
83138
Traceback (most recent call last):
84-
File "21401.py", line 26, in <module>
85-
print "x.next()==>", x.next()
86-
File "21401.py", line 21, in next
139+
File "F:\MyGitHub\StarterLearningPython\2code\21401.py", line 32, in <module>
140+
x.next()
141+
File "F:\MyGitHub\StarterLearningPython\2code\21401.py", line 21, in next
87142
raise StopIteration()
88143
StopIteration
89144

90-
说明什么呢?`print list(x)`将对象返回值都装进了列表中并打印出来,这个正常运行了。此时指针已经移动到了迭代对象的最后一个,正如在[《迭代》](./128.md)中描述的那样,`next()`方法没有检测也不知道是不是要停止了,它还要继续下去,当继续下一个的时候,才发现没有元素了,于是返回了`StopIteration()`
91-
92-
为什么要将用这种可迭代的对象呢?就像上面例子一样,列表不是挺好的吗?
93-
94-
列表的确非常好,在很多时候效率很高,并且能够解决相当普遍的问题。但是,不要忘记一点,在某些时候,列表可能会给你带来灾难。因为在你使用列表的时候,需要将列表内容一次性都读入到内存中,这样就增加了内存的负担。如果列表太大太大,就有内存溢出的危险了。这时候需要的是迭代对象。比如斐波那契数列(在本教程多处已经提到这个著名的数列:[《练习》的练习4](./129.md)[《函数(4)》中递归举例](./204.md)):
145+
`next()`或者`__next__()`中的`self.i <= self.n`为假,就`raise StopIteration()`,结束迭代过程。
146+
147+
还记得斐波那契数列吗?前文已经多次用到,这里我们再次使用它,不过是要用它来做一个迭代器对象。
95148

96149
#!/usr/bin/env python
97150
# coding=utf-8
98151
"""
99152
compute Fibonacci by iterator
100153
"""
101-
__metaclass__ = type
102154

103-
class Fibs:
155+
class Fibs(object): #Python 3: class Fibs:
104156
def __init__(self, max):
105157
self.max = max
106158
self.a = 0
@@ -109,7 +161,7 @@
109161
def __iter__(self):
110162
return self
111163

112-
def next(self):
164+
def next(self): #Python 3: def __next__(self):
113165
fib = self.a
114166
if fib > self.max:
115167
raise StopIteration
@@ -118,7 +170,7 @@
118170

119171
if __name__ == "__main__":
120172
fibs = Fibs(5)
121-
print list(fibs)
173+
print list(fibs) #Python 3: print(list(fibs))
122174

123175
运行结果是:
124176

@@ -127,16 +179,61 @@
127179

128180
>给读者一个思考问题:要在斐波那契数列中找出大于1000的最小的数,能不能在上述代码基础上改造得出呢?
129181
130-
关于列表和迭代器之间的区别,还有两个非常典型的内建函数:`range()``xrange()`,研究一下这两个的差异,会有所收获的。
182+
以上演示了迭代器的一个具体应用。综合本节上面的内容和前文对迭代的讲述,对迭代器做一个概括:
183+
184+
1. 在 Python 中,迭代器是遵循迭代协议的对象。
185+
2. 可以使用`iter()` 以从任何序列得到迭代器(如 list, tuple, dictionary, set 等)。
186+
3. 编写类,实现`__iter__()`方法,以及 `next()`(Python 2)或`__next__()`(Python 3) 。当没有元素时,则引发 `StopIteration`异常。
187+
4. 如果有很多值,列表就会占用太多的内存,而迭代器则占用更少内存。
188+
5. 迭代器从第一个元素开始访问,直到所有的元素被访问完结束,只能往前不会后退。
189+
190+
迭代器不仅实用,也很有趣。看下面的操作:
131191

132-
range(...)
133-
range(stop) -> list of integers
134-
range(start, stop[, step]) -> list of integers
192+
>>> my_lst = [x**x for x in range(4)]
193+
>>> my_lst
194+
[1, 1, 4, 27]
195+
>>> for i in my_lst: print i #Python 3: print(i)
135196

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__']
197+
1
198+
1
199+
4
200+
27
201+
>>> for i in my_lst: print i
202+
203+
1
204+
1
205+
4
206+
27
207+
208+
我连续两次调用列表`my_lst`进行循环,都能正常进行。这个列表相当于一个耐用品,可以反复使用。
138209

139-
`range()`的帮助文档和方法中可以看出,它的结果是一个列表。但是,如果用`help(xrange)`查看:
210+
在Python中,除了列表解析式,还可以做元组解析式,方法非常简单:
211+
212+
>>> my_tup = (x**x for x in range(4))
213+
>>> my_tup
214+
<generator object <genexpr> at 0x02B7C2B0>
215+
>>> for i in my_tup: print i
216+
217+
1
218+
1
219+
4
220+
27
221+
>>> for i in my_tup: print i
222+
223+
对于`my_tup`,我们已经看到,它是generator对象,关于这个名称先不管它,后面会讲解。当把它用到循环中,它明显是一次性用品,只能使用一次,再次使用,就什么也不显示了。
224+
225+
>>> type(my_lst)
226+
<type 'list'>
227+
>>> type(my_tup)
228+
<type 'generator'>
229+
230+
`my_lst``my_tup`是两种不同的对象,并且`my_tup`也不是元组,它是一个generator。其它先不管,请读者在你的Python交互模式中输入`dir(my_tup)`,如果是Python 2,请查找是否有`__iter__``next`;如果是Python 3则查看是否有`__iter__``__next__`。答案是肯定的。这也是`my_lst``my_tup`所引用对象的区别。
231+
232+
因此,`my_tup`引用的是一个迭代器对象。它的`next()`或者`__next__()`方法,使得它只能向前。
233+
234+
关于列表和迭代器之间的区别,还有两个非常典型的内建函数:`range()``xrange()`,研究一下这两个的差异,会有所收获的。
235+
236+
`range()`的结果是一个列表。但是,如果用`help(xrange)`查看(仅限于Python 2):
140237

141238
class xrange(object)
142239
| xrange(stop) -> xrange object
@@ -146,14 +243,16 @@
146243
| generates the numbers in the range on demand. For looping, this is
147244
| slightly faster than range() and more memory efficient.
148245

149-
`xrange()`返回的是对象,并且进一步告诉我们,类似`range()`但不是列表。在循环的时候,它跟`range()`相比“slightly faster than range() and more memory efficient”,稍快并更高的内存效率(就是省内存呀)。查看它的方法:
246+
`xrange()`类似`range()`但返回的不是列表。在循环的时候,它跟`range()`相比“slightly faster than range() and more memory efficient”,稍快并更高的内存效率(就是省内存呀)。查看它的方法:
150247

151248
>>> dir(xrange)
152249
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__len__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
153250

154251
看到令人兴奋的`__iter__`了吗?说明它是可迭代的,它返回的是一个可迭代的对象。
155252

156-
也就是说,通过`range()`得到的列表,会一次性被读入内存,而`xrange()`返回的对象,则是需要一个数值才从返回一个数值。比如这样一个应用:
253+
也就是说,通过`range()`得到的列表,会一次性被读入内存,而`xrange()`返回的对象,则是需要一个数值才从返回一个数值。
254+
255+
上述论述仅适用于Python 2,因为在Python 3里面,将`range()`优化了,相当于Python 2里面`xrange()`,所以,在Python 3中就不再有`xrange()`
157256

158257
还记得`zip()`吗?
159258

@@ -164,7 +263,7 @@
164263

165264
如果两个列表的个数不一样,就会以短的为准了,比如:
166265

167-
>>> zip(range(4), xrange(100000000))
266+
>>> zip(range(4), xrange(100000000)) #适用于Python 2
168267
[(0, 0), (1, 1), (2, 2), (3, 3)]
169268

170269
第一个`range(4)`产生的列表被读入内存;第二个是不是也太长了?但是不用担心,它根本不会产生那么长的列表,因为只需要前4个数值,它就提供前四个数值。如果你要修改为`range(100000000)`,就要花费时间了,可以尝试一下哦。
@@ -173,6 +272,6 @@
173272

174273
------
175274

176-
[总目录](./index.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[上节:特殊方法(2)](./213.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[下节:生成器](./215.md)
275+
[总目录](./index.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[上节:黑魔法](./240.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[下节:生成器](./215.md)
177276

178277
如果你认为有必要打赏我,请通过支付宝:**[email protected]**,不胜感激。

2code/21401.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,36 @@
66
"""
77
class MyRange(object):
88
def __init__(self, n):
9-
self.i = 0
9+
self.i = 1
1010
self.n = n
1111

1212
def __iter__(self):
1313
return self
1414

1515
def next(self):
16-
if self.i < self.n:
16+
if self.i <= self.n:
1717
i = self.i
1818
self.i += 1
1919
return i
2020
else:
2121
raise StopIteration()
2222

2323
if __name__ == "__main__":
24-
x = MyRange(7)
25-
print list(x)
26-
print "x.next()==>", x.next()
27-
print "x.next()==>", x.next()
28-
print "------for loop--------"
29-
for i in x:
30-
print i
24+
x = MyRange(3)
25+
print "self.n=",x.n,";","self.i=",x.i
26+
x.next()
27+
print "self.n=",x.n,";","self.i=",x.i
28+
x.next()
29+
print "self.n=",x.n,";","self.i=",x.i
30+
x.next()
31+
print "self.n=",x.n,";","self.i=",x.i
32+
x.next()
33+
print "self.n=",x.n,";","self.i=",x.i
34+
35+
#print [i for i in x]
36+
#print list(x)
37+
#print "x.next()==>", x.next()
38+
#print "x.next()==>", x.next()
39+
#print "------for loop--------"
40+
#for i in x:
41+
# print i

0 commit comments

Comments
 (0)