我正在参加「启航方案」

写在之前

今日来讲讲「迭代器」的内容,其完成已拖了好多天了,感觉再不写就要忘记了。「迭代」相信对你来说现已不陌生了,我前面曾经专门用一篇文章来讲,假如你现已没有什么印象的话,就再点进去看看(零根底学习 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),检查它是否有 iternext,我能够先告知你,是有的。

既然是有的,那么 tuple1 引证的就是一个迭代器的目标,它的 next() 办法促进它只能向前。

写在之后

迭代器到这就写完了,从内容来看迭代器确实有其过人之处,可是它不是全能的,比方它只能向前,不能回退。还有一个是迭代器并不适合在多线程环境中对可变集合运用,现在这个东西看起来或许仍是有点困难,假如今后有机会写多线程的话,再做解释。

大众号,程序员喵大人,欢迎来撩,有更多根底Python学习思路。