迭代器 (Iterator)

迭代和可迭代

迭代器这个概念在很多语言中(比如 C++,Java)都是存在的,但是不同语言实现迭代器的方式各不相同。在 Python 中,迭代器是指遵循迭代器协议(iterator protocol)的对象。至于什么是迭代器协议,稍后自然会说明。为了更好地理解迭代器,我先介绍和迭代器相关的两个概念:

  • 迭代(Iteration)
  • 可迭代对象(Iterable)

你可能会觉得这是在玩文字游戏,但这确实是要搞清楚的。

当我们用一个循环(比如 for 循环)来遍历容器(比如列表,元组)中的元素时,这种遍历的过程就叫迭代

在 Python 中,我们使用 for...in... 进行迭代。比如,遍历一个 list:

  1. numbers = [1, 2, 3, 4]
  2. for num in numbers:
  3. print num

像上面这种可以使用 for 循环进行迭代的对象,就是可迭代对象,它的定义如下:

含有 __iter__() 方法或 __getitem__() 方法的对象称之为可迭代对象

我们可以使用 Python 内置的 hasattr() 函数来判断一个对象是不是可迭代的:

  1. >>> hasattr((), '__iter__')
  2. True
  3. >>> hasattr([], '__iter__')
  4. True
  5. >>> hasattr({}, '__iter__')
  6. True
  7. >>> hasattr(123, '__iter__')
  8. False
  9. >>> hasattr('abc', '__iter__')
  10. False
  11. >>> hasattr('abc', '__getitem__')
  12. True

另外,我们也可使用 isinstance() 进行判断:

  1. >>> from collections import Iterable
  2. >>> isinstance((), Iterable) # 元组
  3. True
  4. >>> isinstance([], Iterable) # 列表
  5. True
  6. >>> isinstance({}, Iterable) # 字典
  7. True
  8. >>> isinstance('abc', Iterable) # 字符串
  9. True
  10. >>> isinstance(100, Iterable) # 数字
  11. False

可见,我们熟知的字典(dict)、元组(tuple)、集合(set)和字符串对象都是可迭代的。

迭代器

现在,让我们看看什么是迭代器(Iterator)。上文说过,迭代器是指遵循迭代器协议(iterator protocol)的对象。从这句话我们可以知道,迭代器是一个对象,但比较特别,它需要遵循迭代器协议,那什么是迭代器协议呢?

迭代器协议(iterator protocol)是指要实现对象的 __iter()__next() 方法(注意:Python3 要实现 __next__() 方法),其中,__iter()__ 方法返回迭代器对象本身,next() 方法返回容器的下一个元素,在没有后续元素时抛出 StopIteration 异常。

接下来讲讲迭代器的例子,有什么常见的迭代器呢?列表是迭代器吗?字典是迭代器吗?我们使用 hasattr() 进行判断:

  1. >>> hasattr((1, 2, 3), '__iter__')
  2. True
  3. >>> hasattr((1, 2, 3), 'next') # 有 __iter__ 方法但是没有 next 方法,不是迭代器
  4. False
  5. >>>
  6. >>> hasattr([1, 2, 3], '__iter__')
  7. True
  8. >>> hasattr([1, 2, 3], 'next')
  9. False
  10. >>>
  11. >>> hasattr({'a': 1, 'b': 2}, '__iter__')
  12. True
  13. >>> hasattr({'a': 1, 'b': 2}, 'next')
  14. False

同样,我们也可以使用 isinstance() 进行判断:

  1. >>> from collections import Iterator
  2. >>>
  3. >>> isinstance((), Iterator)
  4. False
  5. >>> isinstance([], Iterator)
  6. False
  7. >>> isinstance({}, Iterator)
  8. False
  9. >>> isinstance('', Iterator)
  10. False
  11. >>> isinstance(123, Iterator)
  12. False

可见,虽然元组、列表和字典等对象是可迭代的,但它们却不是迭代器!对于这些可迭代对象,可以使用 Python 内置的 iter() 函数获得它们的迭代器对象,看下面的使用:

  1. >>> from collections import Iterator
  2. >>> isinstance(iter([1, 2, 3]), Iterator) # 使用 iter() 函数,获得迭代器对象
  3. True
  4. >>> isinstance(iter('abc'), Iterator)
  5. True
  6. >>>
  7. >>> my_str = 'abc'
  8. >>> next(my_str) # my_str 不是迭代器,不能使用 next(),因此出错
  9. ---------------------------------------------------------------------------
  10. TypeError Traceback (most recent call last)
  11. <ipython-input-15-5f369cd8082f> in <module>()
  12. ----> 1 next(my_str)
  13. TypeError: str object is not an iterator
  14. >>>
  15. >>> my_iter = iter(my_str) # 获得迭代器对象
  16. >>> isinstance(my_iter, Iterator)
  17. True
  18. >>> next(my_iter) # 可使用内置的 next() 函数获得下一个元素
  19. 'a'

事实上,Python 的 for 循环就是先通过内置函数 iter() 获得一个迭代器,然后再不断调用 next() 函数实现的,比如:

  1. for x in [1, 2, 3]:
  2. print i

等价于

  1. # 获得 Iterator 对象
  2. it = iter([1, 2, 3])
  3. # 循环
  4. while True:
  5. try:
  6. # 获得下一个值
  7. x = next(it)
  8. print x
  9. except StopIteration:
  10. # 没有后续元素,退出循环
  11. break

斐波那契数列迭代器

现在,让我们来自定义一个迭代器:斐波那契(Fibonacci)数列迭代器。根据迭代器的定义,我们需要实现 __iter()__next() 方法(在 Python3 中是 __next__() 方法)。先看代码:

  1. # -*- coding: utf-8 -*-
  2. from collections import Iterator
  3. class Fib(object):
  4. def __init__(self):
  5. self.a, self.b = 0, 1
  6. # 返回迭代器对象本身
  7. def __iter__(self):
  8. return self
  9. # 返回容器下一个元素
  10. def next(self):
  11. self.a, self.b = self.b, self.a + self.b
  12. return self.a
  13. def main():
  14. fib = Fib() # fib 是一个迭代器
  15. print 'isinstance(fib, Iterator): ', isinstance(fib, Iterator)
  16. for i in fib:
  17. if i > 10:
  18. break
  19. print i
  20. if __name__ == '__main__':
  21. main()

在上面的代码中,我们定义了一个 Fib 类,用于生成 Fibonacci 数列。在类的实现中,我们定义了 __iter__ 方法,它返回对象本身,这个方法会在遍历时被 Python 内置的 iter() 函数调用,返回一个迭代器。类中的 next() 方法用于返回容器的下一个元素,当使用 for 循环进行遍历的时候,就会使用 Python 内置的 next() 函数调用对象的 next 方法(在 Python3 中是 __next__ 方法)对迭代器进行遍历。

运行上面的代码,可得到如下结果:

  1. isinstance(fib, Iterator): True
  2. 1
  3. 1
  4. 2
  5. 3
  6. 5
  7. 8

小结

  • 元组、列表、字典和字符串对象是可迭代的,但不是迭代器,不过我们可以通过 iter() 函数获得一个迭代器对象;
  • Python 的 for 循环实质上是先通过内置函数 iter() 获得一个迭代器,然后再不断调用 next() 函数实现的;
  • 自定义迭代器需要实现对象的 __iter()__next() 方法(注意:Python3 要实现 __next__() 方法),其中,__iter()__ 方法返回迭代器对象本身,next() 方法返回容器的下一个元素,在没有后续元素时抛出 StopIteration 异常。

参考资料