从循环说起

顺序分支循环是编程语言的三大逻辑结构,在Python中都得到了支持,而Python更是为循环结构提供了非常便利的语法:for ... in ...

刚从C语言转入Python的同学可能倾向于写索引下标式的循环,例如下面的代码像遍历C中的数组一样遍历了一个Python中的列表:

>>> colors = ['black', 'white', 'red', 'blue']
>>> for i in range(len(colors)):
... print(colors[i])
...
black
white
red
blue

但如果将列表(list)替换为集合(set),这个方法就不奏效了:

>>> colors = set(['black', 'white', 'red', 'blue'])
>>> for i in range(len(colors)):
... print(colors[i])
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: 'set' object does not support indexing

这是因为set不像list,string,tuple这些结构,其中的元素从逻辑上讲本身是没有序的,与之类似的还有dict,因而Python在设计set这个内置数据结构时,并没有实现其下标索引。但遍历集合这个操作本身是合理并且常见的,我们可以如下实现对集合的遍历:

>>> colors = set(['black', 'white', 'red', 'blue'])
>>> for color in colors:
... print(color)
...
red
blue
black
white

至于元素输出的顺序为何是这样,这与set的具体实现有关,不在本文的讨论范围内,本文要探究的是,这种for...in...循环到底是如何运作的。


轮到你出场了,迭代器

想知道for循环的运作模式,首先要介绍迭代器(iterator)的概念。迭代器并不是Python独有的概念,事实上,在C++,Java等其他语言中,都可以看到迭代器的身影,站在更高的角度,迭代器不是一个语言特性,而是一种设计模式,它提升了语言的抽象能力和代码的服用,减少程序员的心智负担。为证明这一点,下面我们就以Python中的迭代器进行说明。

在Python中,迭代器泛指一类实现了迭代器协议的对象,具体来说,任何实现了__next__函数的对象都是迭代器,该函数可以通过Python的内置函数next进行调用,该函数的逻辑应该返回数据结构中的下一个迭代对象或抛出StopIteration异常。以下代码是列表迭代器的一个示例,我们暂时不关心该列表迭代器是怎么来的。

>>> colors = ['black', 'white', 'red', 'blue']
>>> color_iterator = iter(colors)
>>> next(color_iterator)
'black'
>>> next(color_iterator)
'white'
>>> next(color_iterator)
'red'
>>> next(color_iterator)
'blue'
>>> next(color_iterator)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

可以看到,每次对迭代器调用next函数,迭代器或返回应被迭代的下一个对象,或抛出一个StopIteration异常,上层的调用者可通过捕获该异常得知迭代已经结束。


他来了,可迭代对象

可迭代对象(Iterable)是另一个重要概念,顾名思义,它应该泛指满足可以被迭代,即拥有迭代器的一类对象。在Python的迭代器协议里,任何实现了__iter__函数的对象都是可迭代对象,该函数可以通过Python的内置函数iter调用,该函数逻辑上应该返回一个迭代器对象,即返回一个实现了__next__函数的对象。我们还是以列表作为例子。

>>> colors = ['black', 'white', 'red', 'blue']
>>> type(colors)
<class 'list'>
>>> color_iterator = iter(colors)
>>> type(color_iterator)
<class 'list_iterator'>

可以看到,colorscolor_iterator是两个不同的对象,后者是前者的__iter__函数返回的一个新对象。


为什么要这么复杂?

你可能已经在问了,为什么要制定这么复杂的所谓协议?是的,对于每一个类来说,按照规定实现__next____iter__方法听上去很麻烦。但是,当很多类都遵循这个协议麻烦一点的时候,对于调用者而言,事情开始变得简单。换句话说,当所有逻辑上可以被迭代的对象都告诉你,”我已经按照迭代器协议实现了协议里要求的方法“,作为调用者,你不必再关心他们怎么实现的,你可以用一个统一的方式去迭代他们,无论这是一个列表,一个字符串,一个集合,还是一个字典。

事实上,for...in...就是这么做的。


揭秘for循环

Python的for...in...循环实际上等价于以下代码

colors = ['black', 'white', 'red', 'blue']

for color in colors:
print(color)
# 等价于
color_iterator = iter(colors)
while True:
try:
color = next(color_iterator)
print(color)
except StopIteration:
break

当我们毫无顾忌地对列表/字符串/集合/字典/...各式各样的数据结构for...in...时,Python并不是像魔法师一样天然地知道这些对象该怎么去遍历,而是按部就班地用iter取出迭代器,用next取出下一个迭代对象,如果捕获StopIteration,就停止迭代。所以,这些功劳来自于这些对象严格地遵从了迭代器协议


自己实现一个可迭代对象

当我们自定义类时,是不是也应该考虑调用者的感受?下面我们就自己实现一个符合迭代器协议的类。

class MyIterable():
def __init__(self):
self.data = ['I', 'love', 'python']
def __iter__(self):
self.index = 0
return self
def __next__(self):
if self.index < len(self.data):
ret = self.data[self.index]
self.index += 1
return ret
else:
raise StopIteration a = MyIterable()
for x in a:
print(x) '''
执行结果:
I
love
Python
'''

解释一下:MyIterable的数据存放在成员变量data中,即实现了__next__的方法,所以本身就是一个迭代器,因而它的__iter__只需要返回self,但在返回之前,将成员变量index归0,意味着从头开始迭代。在__next__中,如果index比元素总数少,则返回该下标索引的数据,并使得索引自增,否则就抛出StopIteration表示迭代结束。

可以看到,在调用者看来,MyIterable的数据内部是如何存放的,一共有多少数据,该怎么访问他们,这些都不用关心,只要MyIterable老老实实地遵守了迭代器协议,一句for...in...就能进行遍历。


总结

我们通过深入解析Pyhton中for循环的工作机制,接触了迭代器可迭代对象这些Python中乃至通用编程中重要的概念。可以说,迭代器具体来说是语言所规定的一种协议,一个具体的对象实现了协议所规定的接口后,就能让自己看上去是一个迭代器,使用者使用起来也只需要按照接口去使用,减少了心智负担;更抽象来说,迭代器是一种设计模式,对于遍历这种编程中非常常用的操作,通过指定某种模式和规则,让代码的开发和调用都变得更加规范,更加方便。

迭代器在Python中的使用当然不止for循环这么简单,在生成器等更高级的语法糖中,迭代器也扮演至关重要的角色,欢迎大家持续关注!

获取最新文章更新,欢迎关注我的个人公众号:StackOverflow Daily

[Python] 迭代器是什么?你每天在用的for循环都依赖它!的更多相关文章

  1. Python迭代器,可迭代对象,生成器

    迭代器 迭代器(iterator)有时又称游标(cursor)是程式设计的软件设计模式,可在容器物件(container,例如链表或阵列)上遍访的界面,设计人员无需关心容器物件的内存分配的实现细节. ...

  2. python迭代器与iter()函数实例教程

    python迭代器与iter()函数实例教程 发布时间:2014-07-16编辑:脚本学堂 本文介绍了python迭代器与iter()函数的用法,Python 的迭代无缝地支持序列对象,而且它还允许程 ...

  3. python迭代器与生成器

    1.迭代器 iterator 迭代器是一种对象类型!可以由生成器生成! 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素.迭 ...

  4. Day04 - Python 迭代器、装饰器、软件开发规范

    1. 列表生成式 实现对列表中每个数值都加一 第一种,使用for循环,取列表中的值,值加一后,添加到一空列表中,并将新列表赋值给原列表 >>> a = [0, 1, 2, 3, 4, ...

  5. Python 迭代器和列表解析

    Python 迭代器和列表解析 1)迭代器 一种特殊的数据结构,以对象形式存在 >>> i1 = l1.__iter__() >>> i1 = iter(l1) 可 ...

  6. Python迭代器,生成器--精华中的精华

    1. 迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外,迭代器的一大 ...

  7. python迭代器以及itertools模块

    迭代器 在python中,迭代器协议就是实现对象的__iter()方法和next()方法,其中前者返回对象本身,后者返回容器的下一个元素.实现了这两个方法的对象就是可迭代对象.迭代器是有惰性的,只有在 ...

  8. Python迭代器详解

    最近在看两个github项目的代码时对迭代器有些困惑,因此写一篇笔记以理清脉络,经过两天的修改完善对迭代器有了一个初步的理解,相信会比网上绝大多数笔记有用些. 一.基础概念 在进行具体的迭代类和迭代函 ...

  9. day13 python迭代器与生成器

    迭代器 字符串.列表.元组.字典.集合都可以被for循环,说明他们都是可迭代的 可迭代协议 : 就是内部实现了__iter__方法 可以被for循环的都是可迭代的,要想可迭代,内部必须有一个__ite ...

随机推荐

  1. [USACO]奶牛会展(背包)

    [USACO]奶牛会展 题目背景 奶牛想证明它们是聪明而风趣的.为此,贝西筹备了一个奶牛博览会,她已经对N 头奶牛进行 了面试,确定了每头奶牛的智商和情商. 题目描述 贝西有权选择让哪些奶牛参加展览. ...

  2. uboot中Kconfig架构的理解

    1./u-boot-2019.07/Kconfig 是顶层Kconfig mainmenu "U-Boot $UBOOTVERSION Configuration"  #这是总me ...

  3. cmd优化

    cmd很方便,很简约,但是看久了也会视觉疲劳 因此我们来优化一下cmd 原博文是我的一位同学写的,链接如下:https://www.cnblogs.com/ljsh/p/10877715.html 打 ...

  4. 01-跨域-jsonp原理

    jsonp能跨域,并不是因为它发送了ajax请求,而是利用动态创建的script标签. script标签没有收到浏览器同源策略的限制,是可以跨域访问的. 创建的script标签,把其src指向请求的服 ...

  5. IDEA git 合并多个commit

    当前三个commit,demo1,demo2,demo3 选择demo1右键 选择action 跟着指示操作,最后合并 时间线: Log 框时间线:是从上到下,越来越早. 弹出框时间线:是从上到下,越 ...

  6. 基于MaxCompute InformationSchema进行冷门表热门表访问分析

    一.需求场景分析 在实际的数据平台运营管理过程中,数据表的规模往往随着更多业务数据的接入以及数据应用的建设而逐渐增长到非常大的规模,数据管理人员往往希望能够利用元数据的分析来更好地掌握不同数据表的使用 ...

  7. [CF1167D]Bicolored RBS题解

    模拟两个颜色的扩号层数,贪心,如果是左括号,哪边的层数浅就放那边:如果是右括号,哪边的层数深就放那边. 至于层数的维护,两个int就做掉了 放个代码: #include <cstdio> ...

  8. stack2链栈

    #include<iostream> using namespace std; template <class Object> class Stack{ private: st ...

  9. EXP-00091和IMP-00010报错

    原来导出表的库:database ORACLE_SID=TEST0221 版本: 11.2.0.4需要导进的库:database ORACLE_SID=PROD 版本:10.2.0.1因为字符集的问题 ...

  10. 谈谈vue双向数据绑定问题

    vue是MVVM模型,vueObserver 数据监听器,把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用Object.define ...