专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
Python爱好者社区  ·  面向开发者的 NVIDIA ... ·  2 天前  
Python爱好者社区  ·  42岁,讲师,因为评职称郁郁寡欢,吃了半年的 ... ·  5 天前  
Python中文社区  ·  用 Python 打造加密货币实时价格追踪器 ·  4 天前  
Python开发者  ·  劝大家早点拿下软考证,风口已经很明显了 ·  4 天前  
Python中文社区  ·  看完秒懂!用 Python 快速实现交易策略回测 ·  6 天前  
51好读  ›  专栏  ›  Python开发者

Python 黑魔法:迭代器

Python开发者  · 公众号  · Python  · 2017-02-22 21:10

正文

(点击上方蓝字,快速关注我们)


来源:人世间

www.jianshu.com/p/dcf83643deeb

如有好文章投稿,请点击 → 这里了解详情


因为Python,我见识了优雅。优雅不经在于自己使用,还在于如何设计API给别人使用。


设计 api 的时候,可以利用 python 的描述符完成很多工作,而这些描述符操作,还有一个名字就是“魔法方法”。前面我们介绍了一个装饰器魔法,现在再来认识一下迭代器神功。


迭代器(iterator)是访问集合内元素的一种方式,提供了一种遍历类序列对象的方法。对于一般的序列,利用索引从0一直迭代到序列的最后一个元素。对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。对于字典、文件、自定义对象类型等,可以自定义迭代方式,从而实现对这些对象的遍历。总之,迭起器就是定义了对对象进行遍历的方式。


一些编程语言C,C++需要实现这样的结构。另外一些高级语言python,ruby则把迭代协议在语言层面就实现了。当然,这样的隐藏了一些细节,有时候明明已经使用了,却浑然不知。


iter 函数


python提供了一个iter函数用来生成迭代器。这个方法有两个参数,当只有一个参数的时候,若这个参数是一个容器,则返回这个容器的迭代器对象,若这个参数本身就是一个迭代器,则返回其自身。


In [1]alist = [1, 2, 3, 4]

 

In [2]it = iter(alist)

 

In [3]it

Out[3]: listiterator at 0x102496e10>

 

In [4]it2 = iter(it)

 

In [5]id(it) == id(it2)

Out[5]True


iterator 的特点


迭代器都有一个next方法,每次调用这个方法而实现计数,当然计数不是通过索引实现,调用了next方法只会,迭代指针会指向下一个元素的位置。若下一个元素没有了,则会抛出StopIteration异常。


In [6]it.next()

Out[6]2

 

In [7]it.next()

Out[7]3

 

In [8]it.next()

Out[8]4

 

In [9]it.next()

---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

ipython-input-16-54f0920595b2in module>()

----> 1 it.next()

 

StopIteration:

 

In [10]:


这样做有什么用呢?试想想在迭代指针还没指到的当前元素时候,已经迭代之后的位置元素,那些元素需要计算么?因为只有迭代到当前位置的元素时候,才开始计算元素的值。在迭代之前可以不存在,在迭代之后可以被销毁。实现的迭代器不需要准备所遍历的所有元素,没错,这就是迭代器的一大魅力,惰性计算。


for 循环


知道了迭代器大致的用法,我们来遍历一个迭代器。


In [1]it = iter(range(4))

 

In [2]try:

   ...:     while True:

   ...:         print it.next()

   ...except StopIteration:

   ...:     pass

   ...:

0

1

2

3

 

In [3]:


上面的遍历看上去比较别扭,更不没有python优雅的感觉,或许,想到遍历一个容器,for in循环更优雅


In [3]it = range(4)

 

In [4]for i in it:

   ...:     print i

   ...:

0

1

2

3

 

In [5]:


两次运算的效果几乎一样,那么for循环的机理是什么呢。前面所言python内置实现了各种迭代协议,for循环就是一个很好的例子。for 循环的时候,首先对循环对象实现迭代器包装,返回一个迭代器对象,然后每循环一步,就调用哪个迭代器对象的next方法,循环结束的时候,自动处理了 StopIteration这个异常。for循环是对迭代器进行迭代的语法糖。无处不在的语法糖。


当然实现迭代器的时候,有时候会把索引丢掉,在python可以使用内建函数enumerate获取索引。


iterator 的定义


对于上面的 it 这个迭代器,是通过 iter方法实现的,那么iter函数到底做了什么呢?简而言之,实现了迭代器协议的对象,就是迭代器。什么事迭代器协议呢?再简而言之,满足下面两个条件即可:


  • 实现了魔法方法 __iter__(),返回一个迭代对象,这个对象有一个next()方法,

  • 实现 next() 方法,返回当前的元素,并指向下一个元素的位置,当前位置已经没有元素的时候,抛出StopIteration异常。


前面我们迭代range(4)是从零开始,现在我们实现一个迭代器对象,可以逆序迭代的。


class ReverseList():

 

    def __init__(self, item):

        self.list = range(item)

 

    def __iter__(self):

        return self

 

    def next(self):

 

        try:

            return self.list.pop()

        except:

            raise StopIteration

 

In [1]it = ReverseList(4)

 

In [2]it.next()

Out[2]3

 

In [3]it.next()

Out[3]2

 

In [4]it.next()

Out[4]1

 

In [5]it.next()

Out[5]0

 

In [6]:


更复杂的遍历逻辑,都可以在 next 方法里构造。当然,看到了这里,也就大概知道了迭代器的协议,也已经是python的数据结构实现了的。并且还没见识到惰性计算。其实吧,惰性计算,python有更好的处理魔法,就是生成器,关于生成器,比迭代器神功还有效。


接下来就是用迭代器的迭代之后销毁元素的特性,做一个练习吧。


有一个偶数项的列表 a = ["foo", 2, "bar", 4, "far", 6],希望对每两个相邻的两个元素打包,是为一组, 使得结果如下是这样的 [("foo", 2), ("bar", 4), ("far", 6)]。如果是要打包是每三个一组呢?


有很多方法可以解决,下面使用迭代器进行处理,大概代码如下:


a = ["foo", 2, "bar", 4, "far", 6]

group_adjacent = lambda x, kzip(*([iter(x)] * k))

 

In [1]a = ["foo", 2, "bar", 4, "far", 6]

 

In [2]group_adjacent = lambda x, kzip(*([iter(x)] * k))

 

In [3]group_adjacent(a, 2)

Out[3][('foo', 2), ('bar', 4), ('far', 6)]

 

In [4]:


对于迭代器,python还有很多高级功能,并且还专门有一个itertools标准库用来做迭代器对象的相关处理。


迭代器魔法没有装饰器那么惊艳,却有着魔幻的力量,配合生成器,更有另一番天地。


看完本文有收获?请转发分享给更多人

关注「Python开发者」,提升Python技能