文档章节

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

o
 osc_g8254g7s
发布于 2019/08/20 00:11
字数 1819
阅读 26
收藏 0

精选30+云产品,助力企业轻松上云!>>>

从循环说起

顺序分支循环是编程语言的三大逻辑结构,在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

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Python面试题之Python迭代器

要理解迭代器,首先要从字面意思来说。 迭代 重复 下一次重复基于上一次的结果 软件开发就是典型的迭代更新。 讲迭代,我们就先来模拟一下迭代: 现在让我们使用循环来遍历出一个列表里面的所...

osc_a9fd007s
2018/06/11
2
0
每天一个设计模式·迭代器模式

迭代器模式·原文地址 更多《设计模式系列教程》 更多免费教程 博主按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用(靠这吃饭)和(纯粹喜欢)两种语言实现。诚然,每种设计...

godbmw
2018/11/28
0
0
Python 学习笔记

笔记概述 这个笔记是我为了把学过的python知识记录下来,以便以后查看。由于现在对python的理解还不够深入,我只会记录一些python中有些难理解的部分,而不会去做总结性的概括和归纳。随着p...

seandor
2014/05/20
82
0
详解生成器、迭代器

1.迭代     要搞清楚什么关于迭代器,生成器,可迭代对象,前提是我们要理解何为迭代。   第一,迭代需要重复进行某一操作   第二,本次迭代的要依赖上一次的结果继续往下做,如果中...

xsster
2017/07/25
0
0
python 深入理解迭代器

更深入理解 Python 中的迭代 深入探讨 Python 的 循环来看看它们在底层如何工作,以及为什么它们会按照它们的方式工作。 Python 的 循环不会像其他语言中的 循环那样工作。在这篇文章中,我们...

lvyz0207
2018/05/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何删除使用Python的easy_install安装的软件包? - How do I remove packages installed with Python's easy_install?

问题: Python's easy_install makes installing new packages extremely convenient. Python的easy_install使安装新包非常方便。 However, as far as I can tell, it doesn't implement th......

fyin1314
10分钟前
0
0
如何将逗号分隔的字符串转换为数组? - How to convert a comma separated string to an array?

问题: I have a comma separated string that I want to convert into an array, so I can loop through it. 我有一个逗号分隔的字符串,我想将其转换成数组,因此可以循环遍历它。 Is the...

富含淀粉
40分钟前
13
0
深源恒际:担心个人身份被冒用?不存在!

本文作者:c****t 近日,苟晶被冒名顶替身份参加高考的事件在社会各界掀起广泛热议。事件调查结果公布后,舆论风向逆转,吃瓜群众认为当事人夸大其词消费了公众情绪,一边对当事人所遭遇的不...

百度开发者中心
昨天
5
0
CKEditor 5 + SpringBoot实战(三):SpringData JPA数据持久化

在本系列的文章中,我将介绍如何在Spring Boot Application中使用CKEditor编辑器。介绍的内容包括基本环境的搭建,文件上传,SpringData JPA数据持久化,CKEditor5的安装,CKEditor图片上传,...

树下魅狐
今天
9
0
Confluence 如何查看页面 ID

如果你希望查看页面的 ID 你有 2 个方法。 例如,你希望查看 https://www.cwiki.us/display/CONFLUENCEWIKI/Get+started 页面的 Page ID 的话。 如果你的标题栏没有特殊字符,那么将会使用英...

honeymoose
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部