Loading... # 迭代器和生成器 - 迭代是Python最强大的功能之一,是访问集合元素的一种方式。 - 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。 - 迭代器是一个可以记住遍历的位置的对象。 ## 迭代 其实`for .. in ..`循环遍历的过程就是<b>迭代(iteration)</b>。 其他语言诸如C语言,迭代都是通过下标索引。所以可以看出,Python的for循环迭代抽象程度比其他语言高,不论有没有下标,只要是<b>可迭代对象</b>(后文有介绍),都可以通过它迭代,比如字典。 ## 迭代器 迭代器有两个基本的方法:`iter()` 和 `next()`。 `iter()`用于创建一个<b>迭代器对象</b> `next()`将参数中的迭代器<b>进行一次迭代,并返回迭代器中的元素</b> ```python >>> list = [1, 2, 3] >>> l1 = iter(list) >>> l1 <list_iterator object at 0x000001D5E98476A0> >>> next(l1) 1 >>> next(l1) 2 >>> next(l1) 3 >>> next(l1) Traceback (most recent call last): File "<input>", line 1, in <module> StopIteration ``` 迭代器到达结尾时,如果再调用next(),则会无法返回值而报错`StopIteration`。 ## 为自定义类创建迭代器 把一个类作为一个迭代器使用需要在类中实现两个方法 `__iter__()` 与 `__next__()` 。 ```python class my_nums(): def __iter__(self): self.a = 1 return self def __next__(self): if self.a < 20: x = self.a self.a += 1 return x else: raise StopIteration mn = my_nums() l1 = iter(mn) print(next(l1)) print(next(l1)) print(next(l1)) for i in mn: print(i, end=' ') ``` > 1 > 2 > 3 > 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - 定义过两个方法后的类的对象便成了可迭代对象,可以对其调用`iter()`和`next()`以及对其进行for循环遍历 - StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在` __next__()` 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。 ## 生成器 通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。 !!! 所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为<b><font color = 'blue'>生成器(generator)</font></b>。 (转自廖雪峰) !!! 生成器是一个特殊的程序,可以被用作控制循环的迭代行为,<b>Python中生成器是迭代器的一种。</b> > <b>延迟计算或惰性求值 (Lazy evaluation)</b> > 迭代器不要求你事先准备好整个迭代过程中所有的元素。仅仅是在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合。 创建生成器的方法有很多。 ### 列表生成器 回忆[列表解析](https://blog.csdn.net/irimsky/article/details/95522381#324_font_size__6font_126)的内容。 ```python squares = [x ** 2 for x in range(10)] ``` 第一种创建生成器的方法是:只要把一个列表解析式的[]改成(),就创建了一个generator: ```python >>> L = [x ** 2 for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x ** 2 for x in range(10)) >>> g <generator object <genexpr> at 0x1022ef630> ``` !!! 如上所言,<b><font color = 'red'>生成器就是迭代器</font></b>,他可以被next()调用。 !!! ```python >>> g = (x ** 2 for x in range(10)) >>> next(g) 0 >>> next(g) 1 >>> next(g) 4 ``` !!! 但是我们很少对生成器使用next(),大多数情况都是使用for循环,因为<b><font color='red'>生成器也是个可迭代对象</b></font>。 !!! ```python g = (x ** 2 for x in range(10)) for i in g: print(i, end=', ') ``` > 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ### 生成器函数 另一个方法是:生成器函数。 !!! 在 Python 中,使用了 `yield` 的函数被称为<b>生成器函数(generator function)</b>。 跟普通函数不同的是,生成器函数是一个<b><font color = 'red'>返回迭代器的函数</font></b>,只能用于迭代操作。 !!! ```python def odd(): print('step 1') yield 1 print('step 2') yield 3 print('step 3') yield 5 o = odd() print(next(o)) print(next(o)) print(next(o)) print(next(o)) ``` > step 1 > 1 > step 2 > 3 > step 3 > 5 > Traceback (most recent call last): > File "D:/Pytest/1.py", line 48, in <module> > print(next(o)) > StopIteration 在调用生成器运行的过程中,<b>每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。</b> 比如上述函数: !!! odd()返回的是一个<u>迭代器</u>,传递给变量o !!! 2. 第一次调用next():先打印step 1 ,再返回1,保存信息并停止运行函数。 2. 第二次调用next():函数从原先停止的地方继续,先打印step 2, 再返回3,保存信息并停止。 3. 第三次调用next():... 4. 第四次调用next():函数已到达末尾,无法返回值,报错StopIteration 同理,生成器函数可以用for循环迭代: ```python # 例1 输出奇数 def odd(n): cnt = 0 while cnt < n: yield cnt * 2 + 1 cnt += 1 for i in odd(5): print(i) o = odd(4) for i in o: print(i) ``` > 1 > 3 > 5 > 7 > 9 > 1 > 3 > 5 > 7 我们将刚才的odd函数改编了一下,使其可以用传递参数来控制其循环次数。 ```python # 例2 杨辉三角形 def triangle(): a = [1] while True: yield a a = [1] + [a[i]+a[i-1] for i in range(1, len(a))] + [1] n = 0 results = [] for t in triangle(): print(t) results.append(t) n = n + 1 if n == 10: break ``` > [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] 上述代码在for循环中控制迭代次数。 ## 可迭代对象 只要是实现了`__iter__()`或`__getitem__()`方法的对象,就可以使用迭代器进行访问。 - 序列:字符串、列表、元组 - 非序列:字典、文件 - **自定义类**:用户自定义的类实现了`__iter__()`或`__getitem__()`方法的对象 - **生成器**或者**生成器函数** 可以使用`isinstance()`判断一个对象是否是Iterable可迭代对象: ```python >>> from collections import Iterable >>> isinstance([], Iterable) # 列表 True >>> isinstance({}, Iterable) # 字典 True >>> isinstance('abc', Iterable) # 字符串 True >>> isinstance((x for x in range(10)), Iterable) # 这个是生成器 True >>> isinstance(100, Iterable) # 普通数字 False ``` - 可迭代≠是迭代器。除了<b>生成器既是迭代器,也是可迭代对象</b>,其他的可迭代对象都需要`iter()`来获取其迭代器。 最后修改:2021 年 08 月 01 日 06 : 19 PM © 允许规范转载