2.7 迭代

跟一些比较牛的程序员交流,经常听到他们嘴里会冒出不标准的英文单词,而若loop、iterate、traversal和recursion不在其内,总觉得他还不够牛。真正牛的程序员只是说“循环、迭代、遍历、递归”,然后再问“这个你懂吗?”。那么大神程序员是什么样子呢?他是扫地僧,大隐隐于市。

先搞清楚这些名词:

  • 循环(loop),指的是在满足条件的情况下,重复执行同一段代码。比如,while语句。
  • 迭代(iterate),指的是按照某种顺序逐个访问对象中的每一项。比如,for语句。
  • 递归(recursion),指的是一个函数不断调用自身的行为。比如,以编程方式输出著名的斐波纳契数列。
  • 遍历(traversal),指的是按照一定的规则访问树形结构中的每个节点,而且每个节点都只访问一次。对于这四个听起来高深莫测的词汇,其实前面已经涉及了一个——循环(loop),本节主要介绍一下迭代(iterate),在网上搜索一下就会发现,对迭代和循环、递归进行比较的文章不少,分别从不同角度将它们进行了对比。这里暂不比较,先搞明白Python中的迭代。

当然,迭代的话题会很长,本着循序渐进的原则,这里介绍比较初级的。

2.7.1 迭代工具

要访问对象中的每个元素,可以这么做(例如一个list):

  1. >>> lst
  2. ['q', 'i', 'w', 's', 'i', 'r']
  3. >>> for i in lst:
  4. ... print i,
  5. ...
  6. q i w s i r

除了这种方法,还可以这样:

  1. >>> lst_iter = iter(lst) #对原来的list实施了一个iter()
  2. >>> lst_iter.next() #要不厌其烦地一个一个手动访问
  3. 'q'
  4. >>> lst_iter.next()
  5. 'i'
  6. >>> lst_iter.next()
  7. 'w'
  8. >>> lst_iter.next()
  9. 's'
  10. >>> lst_iter.next()
  11. 'i'
  12. >>> lst_iter.next()
  13. 'r'
  14. >>> lst_iter.next()
  15. Traceback (most recent call last):
  16. File "<stdin>", line 1, in <module>
  17. StopIteration

iter()是一个内建函数。

next()就是要获得下一个元素,但是作为一名优秀的程序员,最佳品质就是“懒惰”,当然不能这样一个一个地敲,于是:

  1. >>> while True:
  2. ... print lst_iter.next()
  3. ...
  4. Traceback (most recent call last): #报错,而且错误跟前面一样,什么原因
  5. File "<stdin>", line 2, in <module>
  6. StopIteration

先不管错误,再来一遍。

  1. >>> lst_iter = iter(lst) #错误暂且搁置,回头再研究
  2. >>> while True:
  3. ... print lst_iter.next()
  4. ...
  5. q #果然自动化地读取了
  6. i
  7. w
  8. s
  9. i
  10. r
  11. Traceback (most recent call last): #读取到最后一个之后,报错,停止循环
  12. File "<stdin>", line 2, in <module>
  13. StopIteration

首先了解一下上面用到的那个内置函数:iter(),官方文档中有这样一段话描述之:

iter(o[,sentinel])

Return an iterator object.The first argument is interpreted very differently depending on the presence of the second argument.Without a second argument,o must be a collection object which supports the iteration protocol(the iter()method),or it must support the sequence protocol(the getitem()method with integer arguments starting at 0).If it does not support either of those protocols,TypeError is raised.If the second argument,sentinel,is given,then o must be a callable object.The iterator created in this case will call o with no arguments for each call to its next()method;if the value returned is equal to sentinel,StopIteration will be raised,otherwise the value will be returned.

提炼一下此段主要的东西:

  • 返回值是一个迭代器对象。
  • 参数需要是一个符合迭代协议的对象或者是一个序列对象。
  • next()配合与之使用。我们常常将那些能够用诸如循环语句之类的方法来一个一个地读取元素的对象,就称为可迭代的对象。那么用来循环的(如for)就称为迭代工具。

用严格一点的语言说:所谓迭代工具,就是能够按照一定顺序扫描迭代对象的每个元素(按照从左到右的顺序)。

显然,除了for之外,还有别的迭代工具。

那么,刚才介绍的iter()的功能呢?它与next()配合使用,也有实现上述迭代工具的作用。

在Python中,甚至在其他的语言中,关于迭代的说法比较乱,主要是名词乱,刚才我们说,那些能够实现迭代的工具,称为迭代工具,就是这些迭代工具,不少程序员都喜欢叫作迭代器。当然,这都是汉语翻译,英语就是iterator。

从例子中会发现,如果用for来迭代,当到末尾的时候就自动结束了,不会报错。如果用iter()…next()迭代,当最后一个完成之后不会自动结束,还要继续向下,但是后面没有元素了,于是就报一个称之为StopIteration的错误(这个错误的名字叫作停止迭代,这哪里是报错,分明是警告)。

读者还要关注iter()…next()迭代的一个特点。当迭代对象lst_iter被迭代结束,即每个元素都读取了一遍之后,指针就移动到了最后一个元素的后面。如果再访问,指针并没有自动返回到首位置,而是仍然停留在末位置,所以报StopIteration,想要再开始,就需要重新载入迭代对象。所以,当我在上面重新进行迭代对象赋值之后,又可以继续了。这种情况在for等类型的迭代工具中是没有的。

2.7.2 文件迭代器

有一个文件,名称是208.txt,其内容如下:

  1. Learn python with qiwsir.
  2. There is free python course.
  3. The website is:
  4. http://qiwsir.github.io
  5. Its language is Chinese.

用迭代器来操作这个文件:

  1. >>> f = open("208.txt")
  2. >>> f.readline() #读第一行
  3. 'Learn python with qiwsir.\n'
  4. >>> f.readline() #读第二行
  5. 'There is free python course.\n'
  6. >>> f.readline() #读第三行
  7. 'The website is:\n'
  8. >>> f.readline() #读第四行
  9. 'http://qiwsir.github.io\n'
  10. >>> f.readline() #读第五行,也就是最后一行
  11. 'Its language is Chinese.\n'
  12. >>> f.readline() #无内容了,但是不报错,返回空
  13. ''

以上演示的是用readline()一行一行地读。当然,在实际操作中,我们是绝对不能这样做的,一定要让它自动进行,比较常用的方法是:

  1. >>> for line in f:
  2. ... print line,

这段代码之所没有打印出东西来,是因为经过前面的迭代,指针已经移到了最后。这就是迭代的一个特点,要小心指针的位置。

  1. >>> f = open("208.txt") #从头再来
  2. >>> for line in f:
  3. ... print line,
  4. ...
  5. Learn python with qiwsir.
  6. There is free python course.
  7. The website is:
  8. http://qiwsir.github.io
  9. Its language is Chinese.

这种方法是读取文件常用的。另外一个readlines()也可以。但是,需要小心操作。

上面过程用next()也能够实现。

  1. >>> f = open("208.txt")
  2. >>> f.next()
  3. 'Learn python with qiwsir.\n'
  4. >>> f.next()
  5. 'There is free python course.\n'
  6. >>> f.next()
  7. 'The website is:\n'
  8. >>> f.next()
  9. 'http://qiwsir.github.io\n'
  10. >>> f.next()
  11. 'Its language is Chinese.\n'
  12. >>> f.next()
  13. Traceback (most recent call last):
  14. File "<stdin>", line 1, in <module>
  15. StopIteration

如果用next(),就可以直接读取每行的内容,这说明文件是天然的可迭代对象,不需要用iter()转换。

再有,我们用for来实现迭代,在本质上就是自动调用next(),只不过这个工作已经让for偷偷地替我们干了,到这里应该给for取另外一个名字:雷锋。

列表解析也能够作为迭代工具,在研究列表的时候,想必已经清楚了。那么对文件,是否可以用?试一试:

  1. >>> [ line for line in open('208.txt') ]
  2. ['Learn python with qiwsir.\n', 'There is free python course.\n', 'The website is:\n', 'http://qiwsir.github.io\n', 'Its language is Chinese.\n']

至此,看官难道还不为列表解析的强大魅力所折服吗?真的很强大。

其实,迭代器远远不止上述这么简单,下面我们随便列举一些,在Python中还可以这样得到迭代对象中的元素。

  1. >>> list(open('208.txt'))
  2. ['Learn python with qiwsir.\n', 'There is free python course.\n', 'The website is:\n', 'http://qiwsir.github.io\n', 'Its language is Chinese.\n']
  3. >>> tuple(open('208.txt'))
  4. ('Learn python with qiwsir.\n', 'There is free python course.\n', 'The website is:\n', 'http://qiwsir.github.io\n', 'Its language is Chinese.\n')
  5. >>> "$$$".join(open('208.txt'))
  6. 'Learn python with qiwsir.\n$$$There is free python course.\n$$$The website is:\n$$$http://qiwsir.github.io\n$$$Its language is Chinese.\n'
  7. >>> a,b,c,d,e = open("208.txt")
  8. >>> a
  9. 'Learn python with qiwsir.\n'
  10. >>> b
  11. 'There is free python course.\n'
  12. >>> c
  13. 'The website is:\n'
  14. >>> d
  15. 'http://qiwsir.github.io\n'
  16. >>> e
  17. 'Its language is Chinese.\n'

上述方式在编程实践中不一定用得上,只是秀一下可以这么做,但不是非要这么做。

字典是否可迭代?可以。读者不妨自己仿照前面的方法摸索一下(其实前面已经用for迭代过了,这次请用iter()…next()手动一步一步迭代)。