一文解开可迭代对象和迭代器的神秘面纱

可迭代对象和迭代器是两种不同的数据类型,它们都在我们的编程中时常可以遇到。当然他们之间也有很大的关联,接下来就让我们把它们搞定。

1.迭代器(Iterator)

迭代器表示的是一个数据流,并不表示一个数据实体,我们可以使用next()方法计算下一个数据。或者说,可以使用next()方法的就是迭代器。

生成器是迭代器,生成器可以使用下面这种方式生成。下面请看具体实例

a = (i for i in range(10))
print(next(a))  #output:0
print(next(a))  #output:1
print(a[4])     
# error:TypeError: 'generator' object is not subscriptable

如代码所示,使用next()方法可以取出生成器的值,但是使用类似于list的切片便会报错,所以说,生成器不是一个数据实体,而是一个数据流,只能按顺序取出数据。

那如何用代码判断这到底是不是迭代器呢?我们可以使用isinstance关键字

import collections
a = (i for i in range(10))
print(type(a))
print(isinstance(a,collections.Iterator))

# output
<class 'generator'>
True

可以看到生成器确实是迭代器。

生成器函数也是迭代器,即使用yield定义的类函数体,下面可以验证一下。

import collections
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
f = fib(8)
print(type(f))
print(isinstance(f,collections.Iterator))

# output
<class 'generator'>
True

2.可迭代对象(Iterable)

可迭代对象指的就是可以迭代的对象,或者说可以作用于for循环的都是可迭代对象。那这就包括list,tuplt,dict,set等类型,也包括上面讲的生成器。是的,它们都可以用于for循环。

a = [1,2,3,4,5]
for x in a:
    print(x)

这对list可以顺利执行,同样,对于其他序列也是同样正确的。

那接下来我们判断一下到底list,set,dict,tuple是不是可迭代类型。判断是不是可迭代类型可以使用isinstance和Iterable对象。

import collections
a = [1,2,3]
print(type(a))
print(isinstance(a,collections.Iterable))

# output:
<class 'list'>
True

可以看到,list确实是可迭代类型。

3.两者之间的关系

事实上,Iterator是Iterable的子类型,Iterator是从Iterable继承下来的。

查看源码可以看到这一点,值得注意的是,next()方法内部正是用__next__()实现的。

# Python Iterator的部分源码
class Iterator(Iterable):

    __slots__ = ()

    @abstractmethod
    def __next__(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        raise StopIteration

    def __iter__(self):
        return self

因此,两者之间关系已经很明了了,迭代器肯定是可迭代对象,但可迭代对象不一定是迭代器。

4.动手实现一个迭代器

我们自己动手写一个有助于我们从原理层面理解迭代器,下面我便带着大家看看迭代器如何实现。

从前面我们展示出来的迭代器的部分源码,我们可以看出来,迭代器首先是继承了Iterable,其次更重要的是迭代器实现了两个虚拟方法,一个是__next__(self),另一个是__iter__(self)方法,其中__iter__(self)是从Iterable中继承下来的。因此实现一个属于自己的迭代器的关键是要实现这两个方法。

class Fib:
    def __init__(self):
        self.prev = 0
        self.curr = 1

    def __iter__(self):
        return self

    def __next__(self):
        value = self.curr
        self.curr += 1
        self.prev = value
        return value

上面这段代码实现了一个简单的从1开始的递增序列的一个迭代器,其中__iter__(self)返回迭代器本身,而__next__(self)实现了如何去取得下一个值。

那么接下来我们来取出下一个值,验证一下我们的迭代器正不正确。

f = Fib()
print(next(f))
print(next(f))
print(f.__next__())
print(f.__next__())

# output:
1
2
3
4

结果正是我们当初设计的迭代器一样。这里使用了next()函数,也用了__next__()方法,其实本质是一样的。只不过next()包装了__next__()方法,是一个全局的函数,而__next__()是一个对象的方法,只能由对象调用。

这样的方法在Python中还有许多,例如我们常用的len()函数,就是这样的设计原理,在对象内部有一个__len__()方法,调用len(),内部实现的就是__len__().

s = 'abcdefg'
print(s.__len__())  #output:7
print(len(s))       #output:7

如代码所示,有两种方法可以求取长度。我们在实际写代码中通常直接使用len()方法,因为这个调用起来更简单,可读性也更加好,这也是为什么Python要提供len()方法的原因,毕竟Python的设计是遵循优雅简洁的原则进行的。

注意:在运行上面代码时,不要将迭代器对象转化为list或者set等数据结构,也就是不要执行list(f),这会使得这个代表着无限序列的迭代器一次性装入内存。我因为不小心执行了这个代码,导致内存使用率到达90%多,最后重启电脑才恢复。

最后

那为什么需要迭代器呢?那是因为我们的内存是有限的,但可能需要表示的数据是无限的,那这个时候我们可以使用迭代器,在Python3.x中,以前所有返回序列的方法,都已经变为返回迭代器了。我们可以将迭代器看作一个懒惰的家伙,他只有会在要使用时才会给你计算和提供一个数据,这可以让我们更省内存,运行效率更高。

原文发布于微信公众号 - 日常学python(daily_learn)

原文发表时间:2018-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码云1024

JAVA 面试基础

31470
来自专栏IMWeb前端团队

标准的Promise

序言 不同项目下lib里的promise/deferred往往是差异化最多的,用起来和自己的习惯相比经常是缺胳膊少腿多屁眼有卵用,因此聊聊标准的Promise的...

20750
来自专栏小白的技术客栈

Python基础语法-常量与变量(重发)

昨天的文章虽然有插图,但是一个都没有显示出来,估计是天气太热,不愿意露面的缘故吧。这些都不是事,暂且不表,今天再次发布与昨天相同的文章,主要为了弥补3个插图。为...

37840
来自专栏水击三千

JavaScript操作符(关系操作符、相等操作符和条件操作符)

关系操作符用于对两个值进行比较,返回一个布尔值。关系操作符包括大于(>),小于(<),大于等于(>=),小于等于(<=)。当关系操作符用于非数值时,也要先进行数...

24890
来自专栏老九学堂

最新Java高薪面试题+答案+解析!

今天老九君给大家分享一些Java面试需要的题目哟~ 01 anonymousinnerclass(匿名内部类)是否可以extends(继承)其它类,是否可以im...

46270
来自专栏用户2442861的专栏

JS面向对象的程序设计

http://www.cnblogs.com/gaojun/p/3386552.html

9910
来自专栏Python爱好者

Java基础笔记07

11930
来自专栏Brian

C++11基础学习系列三

---- 概述 随着自己学习C++11的进度,今天记录和实战C++11的战果。废话少说,直接记录C++11的点滴。 数组 在前面学习系列里面,介绍了模板容器类v...

28540
来自专栏开发与安全

从零开始学C++之异常(二):程序错误、异常(语法、抛出、捕获、传播)、栈展开

一、程序错误 编译错误,即语法错误。程序就无法被生成运行代码。 运行时错误 不可预料的逻辑错误 可以预料的运行异常 例如: 动态分配空间时...

21800
来自专栏从流域到海域

《笨办法学Python》 第6课手记

《笨办法学Python》 第6课手记 第6课讲字符串和文本,作者给出的代码如下: x = "There are %d types of people." % 1...

20450

扫码关注云+社区

领取腾讯云代金券