|
6 | 6 |
|
7 | 7 | 迭代,对于读者已经不陌生了,曾有专门一节来讲述,如果印象不深,请复习[《迭代》](./128.md)。
|
8 | 8 |
|
9 |
| -正如读者已知,对序列(列表、元组)、字典和文件都可以用`iter()`方法生成迭代对象,然后用`next()`方法访问。当然,这种访问不是自动的,如果用for循环,就可以自动完成上述访问了。 |
| 9 | + >>> hasattr(list, '__iter__') |
| 10 | + True |
10 | 11 |
|
11 |
| -如果用`dir(list)`,`dir(tuple)`,`dir(file)`,`dir(dict)`来查看不同类型对象的属性,会发现它们都有一个名为`__iter__`的东西。这个应该引起读者的关注,因为它和迭代器(iterator)、内置的函数iter()在名字上是一样的,除了前后的双下划线。望文生义,我们也能猜出它肯定是跟迭代有关的东西。当然,这种猜测也不是没有根据的,其重要根据就是英文单词,如果它们之间没有一点关系,肯定不会将命名搞得一样。 |
| 12 | +不仅仅是列表,文件、字典都有一个名为`__iter__`的方法,这说明它们都是可迭代的。 |
12 | 13 |
|
13 |
| -猜对了。`__iter__`就是对象的一个特殊方法,它是迭代规则(iterator potocol)的基础。或者说,对象如果没有它,就不能返回迭代器,就没有`next()`方法,就不能迭代。 |
| 14 | +`__iter__()`是对象的一个特殊方法,它是迭代规则(iterator potocol)的基础,有了它,说明对象是可迭代的。 |
14 | 15 |
|
15 |
| ->提醒注意,如果读者用的是python3.x,迭代器对象实现的是`__next__()`方法,不是`next()`。并且,在python3.x中有一个内建函数next(),可以实现`next(it)`,访问迭代器,这相当于于python2.x中的`it.next()`(it是迭代对象)。 |
| 16 | +跟迭代有关的一个内建函数`iter()`,它的文档中这样描述: |
16 | 17 |
|
17 |
| -那些类型是list、tuple、file、dict对象有`__iter__()`方法,标着他们能够迭代。这些类型都是python中固有的,我们能不能自己写一个对象,让它能够迭代呢? |
| 18 | + >>> help(iter) |
| 19 | + Help on built-in function iter in module __builtin__: |
18 | 20 |
|
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强悍,自己写一个迭代器对象。 |
20 | 66 |
|
21 | 67 | #!/usr/bin/env python
|
22 | 68 | # coding=utf-8
|
23 | 69 |
|
24 | 70 | """
|
25 | 71 | the interator as range()
|
26 | 72 | """
|
27 |
| - class MyRange(object): |
| 73 | + class MyRange(object): #Python 3: class MyRange: |
28 | 74 | def __init__(self, n):
|
29 |
| - self.i = 0 |
| 75 | + self.i = 1 |
30 | 76 | self.n = n
|
31 | 77 |
|
32 | 78 | def __iter__(self):
|
33 | 79 | return self
|
34 | 80 |
|
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: |
37 | 83 | i = self.i
|
38 | 84 | self.i += 1
|
39 | 85 | return i
|
|
42 | 88 |
|
43 | 89 | if __name__ == "__main__":
|
44 | 90 | 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()函数,下同,从略 |
50 | 92 |
|
51 | 93 | 将代码保存,并运行,结果是:
|
52 | 94 |
|
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] |
62 | 96 |
|
63 |
| -以上代码的含义,是自己仿写了拥有`range()`的对象,这个对象是可迭代的。分析如下: |
| 97 | +以上代码的含义,是自己仿写了类似`range()`的类,但是跟`range()`又有所不同,除了结果不同之外,还有: |
64 | 98 |
|
65 |
| -类MyRange的初始化方法`__init__()`就不用赘述了,因为前面已经非常详细分析了这个方法,如果复习,请阅读[《类(2)》](./207md)相关内容。 |
| 99 | +- 类`MyRange`的初始化方法`__init__()`就不用赘述了,因为前面已经非常详细分析了这个方法,如果复习,请阅读[《类(2)》](./207md)相关内容。 |
66 | 100 |
|
67 |
| -`__iter__()`是类中的核心,它返回了迭代器本身。一个实现了`__iter__()`方法的对象,即意味着其实可迭代的。 |
| 101 | +- `__iter__()`是类中的核心,它返回了迭代器本身。一个实现了`__iter__()`方法的对象,即意味着它是可迭代的。 |
68 | 102 |
|
69 |
| -含有`next()`的对象,就是迭代器,并且在这个方法中,在没有元素的时候要发起`StopIteration()`异常。 |
| 103 | +- 实现`next()`或者`__next__()`方法,从而使得这个对象是迭代器对象,并且方法中判断,在不满足条件的时候要发起`StopIteration()`异常。 |
70 | 104 |
|
71 |
| -如果对以上类的调用换一种方式: |
| 105 | +再来看`range()`(以下仅仅限于Python 2): |
72 | 106 |
|
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] |
77 | 114 |
|
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 |
79 | 137 |
|
80 |
| - $ python 21401.py |
81 |
| - [0, 1, 2, 3, 4, 5, 6] |
82 |
| - x.next()==> |
83 | 138 | 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 |
87 | 142 | raise StopIteration()
|
88 | 143 | StopIteration
|
89 | 144 |
|
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 | +还记得斐波那契数列吗?前文已经多次用到,这里我们再次使用它,不过是要用它来做一个迭代器对象。 |
95 | 148 |
|
96 | 149 | #!/usr/bin/env python
|
97 | 150 | # coding=utf-8
|
98 | 151 | """
|
99 | 152 | compute Fibonacci by iterator
|
100 | 153 | """
|
101 |
| - __metaclass__ = type |
102 | 154 |
|
103 |
| - class Fibs: |
| 155 | + class Fibs(object): #Python 3: class Fibs: |
104 | 156 | def __init__(self, max):
|
105 | 157 | self.max = max
|
106 | 158 | self.a = 0
|
|
109 | 161 | def __iter__(self):
|
110 | 162 | return self
|
111 | 163 |
|
112 |
| - def next(self): |
| 164 | + def next(self): #Python 3: def __next__(self): |
113 | 165 | fib = self.a
|
114 | 166 | if fib > self.max:
|
115 | 167 | raise StopIteration
|
|
118 | 170 |
|
119 | 171 | if __name__ == "__main__":
|
120 | 172 | fibs = Fibs(5)
|
121 |
| - print list(fibs) |
| 173 | + print list(fibs) #Python 3: print(list(fibs)) |
122 | 174 |
|
123 | 175 | 运行结果是:
|
124 | 176 |
|
|
127 | 179 |
|
128 | 180 | >给读者一个思考问题:要在斐波那契数列中找出大于1000的最小的数,能不能在上述代码基础上改造得出呢?
|
129 | 181 |
|
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 | +迭代器不仅实用,也很有趣。看下面的操作: |
131 | 191 |
|
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) |
135 | 196 |
|
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`进行循环,都能正常进行。这个列表相当于一个耐用品,可以反复使用。 |
138 | 209 |
|
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): |
140 | 237 |
|
141 | 238 | class xrange(object)
|
142 | 239 | | xrange(stop) -> xrange object
|
|
146 | 243 | | generates the numbers in the range on demand. For looping, this is
|
147 | 244 | | slightly faster than range() and more memory efficient.
|
148 | 245 |
|
149 |
| -`xrange()`返回的是对象,并且进一步告诉我们,类似`range()`,但不是列表。在循环的时候,它跟`range()`相比“slightly faster than range() and more memory efficient”,稍快并更高的内存效率(就是省内存呀)。查看它的方法: |
| 246 | +`xrange()`类似`range()`,但返回的不是列表。在循环的时候,它跟`range()`相比“slightly faster than range() and more memory efficient”,稍快并更高的内存效率(就是省内存呀)。查看它的方法: |
150 | 247 |
|
151 | 248 | >>> dir(xrange)
|
152 | 249 | ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__len__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
|
153 | 250 |
|
154 | 251 | 看到令人兴奋的`__iter__`了吗?说明它是可迭代的,它返回的是一个可迭代的对象。
|
155 | 252 |
|
156 |
| -也就是说,通过`range()`得到的列表,会一次性被读入内存,而`xrange()`返回的对象,则是需要一个数值才从返回一个数值。比如这样一个应用: |
| 253 | +也就是说,通过`range()`得到的列表,会一次性被读入内存,而`xrange()`返回的对象,则是需要一个数值才从返回一个数值。 |
| 254 | + |
| 255 | +上述论述仅适用于Python 2,因为在Python 3里面,将`range()`优化了,相当于Python 2里面`xrange()`,所以,在Python 3中就不再有`xrange()`。 |
157 | 256 |
|
158 | 257 | 还记得`zip()`吗?
|
159 | 258 |
|
|
164 | 263 |
|
165 | 264 | 如果两个列表的个数不一样,就会以短的为准了,比如:
|
166 | 265 |
|
167 |
| - >>> zip(range(4), xrange(100000000)) |
| 266 | + >>> zip(range(4), xrange(100000000)) #适用于Python 2 |
168 | 267 | [(0, 0), (1, 1), (2, 2), (3, 3)]
|
169 | 268 |
|
170 | 269 | 第一个`range(4)`产生的列表被读入内存;第二个是不是也太长了?但是不用担心,它根本不会产生那么长的列表,因为只需要前4个数值,它就提供前四个数值。如果你要修改为`range(100000000)`,就要花费时间了,可以尝试一下哦。
|
|
173 | 272 |
|
174 | 273 | ------
|
175 | 274 |
|
176 |
| -[总目录](./index.md) | [上节:特殊方法(2)](./213.md) | [下节:生成器](./215.md) |
| 275 | +[总目录](./index.md) | [上节:黑魔法](./240.md) | [下节:生成器](./215.md) |
177 | 276 |
|
178 | 277 | 如果你认为有必要打赏我,请通过支付宝: **[email protected]**,不胜感激。
|
0 commit comments