前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文解开可迭代对象和迭代器的神秘面纱

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

作者头像
sergiojune
发布2018-10-09 10:31:00
5980
发布2018-10-09 10:31:00
举报
文章被收录于专栏:日常学python日常学python

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

1.迭代器(Iterator)

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

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

代码语言:javascript
复制
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关键字

代码语言:javascript
复制
import collections
a = (i for i in range(10))
print(type(a))
print(isinstance(a,collections.Iterator))

# output
<class 'generator'>
True

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

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

代码语言:javascript
复制
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循环。

代码语言:javascript
复制
a = [1,2,3,4,5]
for x in a:
    print(x)

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

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

代码语言:javascript
复制
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__()实现的。

代码语言:javascript
复制
# 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中继承下来的。因此实现一个属于自己的迭代器的关键是要实现这两个方法。

代码语言:javascript
复制
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)实现了如何去取得下一个值。

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

代码语言:javascript
复制
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__().

代码语言:javascript
复制
s = 'abcdefg'
print(s.__len__())  #output:7
print(len(s))       #output:7

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

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

最后

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 日常学python 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档