写在之前
今天来讲讲「迭代器」的内容, 其实已经拖了好多天了, 感觉再不写就要忘记了.「迭代」相信对你来说已经不陌生了, 我前面曾经专门用一篇文章来讲, 如果你已经没有什么印象的话, 就再点进去看看 (零基础学习 Python 之初识迭代).
迭代器
首先我们先来看一种检查是否可迭代的方法:
- >>> hasattr(list,'__iter__')
- True
可以用上面的这种方法检查已经学习过的其他默认类型的对象, 比如字符串, 列表, 字典等是否是可迭代的.
iter() 是一个特殊方法, 它是迭代规则的基础, 有了它, 就说明对象是可迭代的. 跟迭代有关的一个内建函数 iter(), 这个函数我们在之前的文章中介绍过, 它返回的是一个迭代器对象, 比如像下面这样:
- >>> list1 = [1,2,3,4]
- >>> iter_list = iter(list1)
- >>> iter_list
- <list_iterator object at 0x00000000021CE438>
从上述代码的结果可以看出, iter_list 引用的是迭代器对象. 那么在这里有一个问题, iter_list 和 list1 有区别吗? 我们来试一下:
- >>> hasattr(list1,'__iter__')
- True
- >>> hasattr(iter_list,'__iter__')
- True
从上面看出它们都有 iter, 说明它们都是可迭代的.
- >>> hasattr(list1,"__next__")
- False
- >>> hasattr(iter_list,"__next__")
- True
我们把像 iter_list 所引用的对象那样, 称之为「迭代器对象」. 显而易见的是, 迭代器对象必然是可迭代的, 反正则不一定. 且 Python 中迭代器对象实现的是 next() 方法.
为了体现一下 Python 在这的强大之处, 我们先来写一个迭代器对象:
- class MyRange:
- def __init__(self,n):
- self.i = 1
- self.n = n
- def __iter__(self):
- return self
- def __next__(self):
- if self.i <= self.n:
- i = self.i
- self.i += 1
- return i
- else:
- raise StopIteration()
- if __name__ == "__main__":
- x = MyRange(5)
- print([i for i in x])
上述代码的运行结果如下所示:
[1,2,3,4,5]
上述的代码仿写了类似 range() 的类, 但是与 range() 又有所不同, 除了结果不同以外还包括以下 2 点:
1.__iter__() 是类中的核心, 它返回了迭代器的本身, 一个实现了 iter() 方法的对象, 就意味着它是可迭代的.
2. 实现了 next() 方法, 从而使得这个对象是迭代器对象.
接下来我们来看看 range() 本身:
- >>> a = range(5)
- >>> hasattr(a,'__iter__')
- True
- >>> hasattr(a,'__next__')
- False
- >>> print(a)
- range(0, 5)
由上面我们就可以看出, 其实我们所写的类和 range() 本身还是有很大区别的.
通过上面的内容和我们之前的文章对迭代的讲述, 下面我们对迭代器做一个概括:
1. 在 Python 中, 迭代器是遵循迭代协议的对象. 我们可以使用 iter() 从任何序列得到迭代器 (exp: list,turple,set and so on).
2. 当自己编写迭代器的类的时候, 其中实现 iter() 和 next() 方法, 如果没有元素的话, 会引发 StopIteration 异常.
3. 如果有很多值的话, 列表会占用太多的内存, 而迭代器则占用的更少, 它从第一个元素开始访问, 直到所有的元素被访问完结束, 只能向前冲, 不能后退.
迭代器不仅仅是实用而已, 而且也非常的有趣, 让我们来看下面的操作:
- >>> list1 = [x**x for x in range(3)]
- >>> list1
- [1, 1, 4]
- >>> for i in list1:print(i)
- ...
- 1
- 1
- 4
- >>> for i in list1:print(i)
- ...
- 1
- 1
- 4
我们在上面重复两次调用列表 list1 进行循环, 都是能正常进行的, 这个列表相当于一个可以长久使用的东西, 可以重复使用.
在 Python 中, 除了列表解析式以外, 还可以做成元组解析式, 方法也是非常的简单:
- >>> tuple1 = (x**x for x in range(3))
- >>> tuple1
- <generator object <genexpr> at 0x0000000001DF16D8>
- >>> for i in tuple1:print(i)
- ...
- 1
- 1
- 4
- >>> for i in tuple1:print(i)
- ...
对于 tuple1, 我们可以看到它是一个 generator 对象, 关于这个是啥我们先不管, 后面我会单独来说的. 当我们把它用到循环中的时候, 它明显是个一次性用品, 再次使用的时候它就什么也不显示了.
- >>> type(list1)
- <class 'list'>
- >>> type(tuple1)
- <class 'generator'>
由上面可以看出, list1 和 tuple1 是两种不同的对象, 它们之间的区别不仅仅是 tuple1 是一个元组这么简单, 它还是 generator. 其它的我们先不管, 你可以尝试一下在交互模式下输入 dir(tuple1), 查看它是否有 iter 和 next, 我可以先告诉你, 是有的.
既然是有的, 那么 tuple1 引用的就是一个迭代器的对象, 它的 next() 方法促使它只能向前.
写在之后
迭代器到这就写完了, 从内容来看迭代器确实有其过人之处, 但是它不是万能的, 比如它只能向前, 不能回退. 还有一个是迭代器并不适合在多线程的环境中对可变集合使用, 现在这个东西看起来可能还是有点困难, 如果以后有机会写多线程的话, 再做解释.
更多内容, 欢迎关注公众号「Python 空间」, 期待和你的交流.
来源: https://www.cnblogs.com/Rocky0429/p/10153951.html