可迭代对象和迭代器是两种不同的数据类型,它们都在我们的编程中时常可以遇到。当然他们之间也有很大的关联,接下来就让我们把它们搞定。
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中,以前所有返回序列的方法,都已经变为返回迭代器了。我们可以将迭代器看作一个懒惰的家伙,他只有会在要使用时才会给你计算和提供一个数据,这可以让我们更省内存,运行效率更高。