一. 可迭代对象(Iterable) 和迭代器(Iterator)
Python中任意的对象, 只要它定义了可以返回一个迭代器的iter方法, 或者定义了可以支持下标索引的getitem方法, 那么它就是一个可迭代对象. 简单说, 可迭代对象就是能提供迭代器的任意对象. 那迭代器又是什么呢?
任意对象, 只要定义了next(Python2) 或者next方法, 它就是一个迭代器. 就这么简单.
现在我们来理解迭代(iteration). 当我们使用一个循环来遍历某个东西时, 这个过程本身就叫迭代.
二. 生成器(Generators)
1. 生成器基本概念
生成器也是一种迭代器, 但是你只能对其迭代一次. 这是因为它们并没有把所有的值存在内存中, 而是在运行时生成值. 你通过遍历来使用它们, 要么用一个“for”循环, 要么将它们传递给任意可以进行迭代的函数和结构. 大多数时候生成器是以函数来实现的. 然而, 它们并不返回一个值, 而是yield一个值. 这里有个生成器函数的简单例子:
def generator_function():
for i in range(10):
yield i
for item in generator_function():
print(item)
输出:
0
1
2
3
4
5
6
7
8
9
这个案例并不是非常实用. 生成器最佳应用场景是:你不想同一时间将所有计算出来的大量结果集分配到内存当中, 特别是结果集里还包含循环. 这样做会消耗大量资源. 许多Python 2里的标准库函数都会返回列表, 而Python 3都修改成了返回生成器, 因为生成器占用更少的资源.
下面是一个计算斐波那契数列的生成器:
# generator version
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
for x in fibon(100):
print(x)
用这种方式, 我们可以不用担心它会使用大量资源. 然而, 之前如果我们这样来实现的话:
def fibon(n):
a = b = 1
result = []
for i in range(n):
result.append(a)
a, b = b, a + b
return result
2. 理解生成器的”生成”
在测试前你需要再知道一个Python内置函数:next(). 它允许我们获取一个序列的下一个元素. 那我们来验证下我们的理解:
def generator_function():
for i in range(3):
yield i
gen = generator_function()
print(next(gen)) # Output: 0
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Error
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
我们可以看到, 在yield掉所有的值后, next()触发了一个StopIteration的异常. 基本上这个异常告诉我们, 所有的值都已经被yield完了. 你也许会奇怪, 为什么我们在使用for循环时没有这个异常呢?答案很简单. for循环会自动捕捉到这个异常并停止调用next().
3. 内置数据类型中的可迭代对象
my_string = "Yasoob"
print(next(my_string))
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: str object is not an iterator
这个异常说那个str对象不是一个迭代器. 对, 就是这样!它是一个可迭代对象, 而不是一个迭代器. 这意味着它支持迭代, 但我们不能直接对其进行迭代操作. 那我们怎样才能对它实施迭代呢?是时候学习下另一个内置函数, iter. 它将根据一个可迭代对象返回一个迭代器对象. 这里是我们如何使用它:
my_iter = iter("Yasoob")
print(next(my_iter))
# Output: 'Y'