python黑魔法---迭代器(iterator)

因为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-54f0920595b2> in <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, k: zip(*([iter(x)] * k))

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

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

In [3]: group_adjacent(a, 2)
Out[3]: [('foo', 2), ('bar', 4), ('far', 6)]

In [4]:

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容