Python 中对象的遍历称为迭代,常用 for 循环和 while 循环等实现。那么有哪些对象可以迭代,它们有什么共同特性呢?本篇对可迭代对象、迭代器和生成器进行介绍。

迭代

对于 python 中的 list, tuple, dict, set, str,文件对象等容器(数据的集合,这里不使用集合为了区分 python 中的 set),我们可以使用 for 循环等遍历里面的元素,但没有按照固定的顺序进行:

1
2
for i in set("a1$."):
print(i) # 1 $ a .

对于 C 语言,迭代都是按照索引顺序进行:

1
2
3
for (i = 0; i < length; i++) {
n = list[i];
}

可见,python 的 for 循环的抽象程度是高于 C 的。因此,在 python 中对于没有索引的容器都可以进行迭代。如

1
2
3
4
5
6
7
8
9
10
11
12
13
d = {"a": 1, "b": 2}
# 对键进行迭代,对字典迭代默认为对键迭代
for k in d:
print(k) # a b
# 或者指定按键迭代
for k in d.keys():
print(k) # a b
# 对值迭代
for v in d.values():
print(v) # a b
# 同时对键和值迭代
for k, v in d.items():
print(k, v) # (a, 1) (b, 2)

想要迭代时增加索引序号:

1
2
3
# 默认从 0 开始索引
for i, k, v in enumerate(d.items()):
print(i, k, v) # (0 ('a', 1)) (1 ('b', 2))

那到底哪些对象可以进行 for 循环或者说可迭代呢?在 python 中,可迭代对象可以进行迭代,所谓可迭代对象就是类中包含有方法 __iter__() 的实例,一般该方法都是通过一些已知的可迭代对象来实现。此外,有些类中包含有方法__getitem__()的也可以进行迭代。

1
2
3
4
5
6
7
8
9
10
11
12
# 包含有 __iter__() 的类
class TestIterable:
def __init__(self):
self.t = range(3)

def __iter__(self):
return iter(self.t)

# for 循环遍历
ti = TestIterable()
for x in ti:
print(x) # 0 1 2
1
2
3
4
5
6
7
8
9
10
11
# 包含有 __getitem__() 的类
class TestGetItem:
def __init__(self):
self.t = range(3)

def __getitem__(self, i):
return self.t[i]
# for 循环遍历
tg = TestGetItem()
for x in tg:
print(x) # 0 1 2

可迭代对象

python 中使用最多的且能够进行 for 循环的对象是可迭代(Iterable)对象,就是类中实现有方法 __iter__()的,那么如何识别哪些对象是可迭代对象呢?可以采用如下方法:

1
2
3
4
5
6
# 判断类中是否有 __iter__ 函数
hasattr(ti, '__iter__') # True

# 判断对象是否是 Iterable 的子类
from collections.abc import Iterable
isinstance(ti, Iterable) # True

这里再次总结一下 python 中哪些对象是可迭代对象:

  1. 容器对象,如 list, tuple, set, dict, str;
  2. 文件对象;
  3. 类中定义了 __iter__()的实例对象;

对于文件对象,判断是否是可迭代对象:

1
2
3
4
5
6
7
8
from pathlib import Path
import os

file = "test.txt"
Path(file).touch()
with open(file, "w") as f:
print(isinstance(f, Iterable)) # True
# os.remove(file)

迭代器

从某种程度上,迭代器(Iterator)是可迭代对象的子集。迭代器就是同时实现了方法__iter__()__next__() 的类。可以使用如下方法判断一个对象是否是迭代器:

1
2
3
4
5
6
# 判断类中是否有 __iter__ 函数和 __next__ 函数
hasattr(ti, '__iter__') and hasattr(ti, '__next__') # True

# 判断对象是否是 Iterator 的子类
from collections.abc import Iterator
isinstance(ti, Iterator) # True

可迭代对象可以使用函数 iter() 转化为迭代器。迭代器对象还可以使用 next() 函数进行访问元素,没有数据是抛出 StopIteration 错误。

1
2
3
4
5
6
a = [0, 1, 2]
to = iter(a)
next(to) # 0
next(to) # 1
next(to) # 2
next(to) # StopIteration:

Python的迭代器对象表示的是一个数据流。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以迭代器的计算是惰性的,只有在需要返回下一个数据时它才会计算。迭代器对象可以表示一个无限大的数据流,例如全体自然数。迭代器持有一个内部状态的字段,用于记录下次迭代返回值。迭代器不会一次性把所有元素加载到内存,而是在需要的时候才生成返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 无限序列
from itertools import count

counter = count(start=0)
next(counter) # 0
next(counter) # 1
next(counter) # 3

# 有限序列生成无限序列
from itertools import cycle

schedule = cycle(['张三', '李四'])
next(schedule) # 张三
next(schedule) # 李四
next(schedule) # 张三

# 无限序列截取有限序列
from itertools import islice

schedule = cycle(["张三", "李四"]) # 无限序列
this_week = islice(schedule, 0, 7) # 有限序列
for name in this_week:
print(name) # 张三 李四 张三 李四 张三 李四 张三

其实,对可迭代对象使用 for 循环,内部就是先将可迭代对象转化为迭代器,然后进行返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Python 的 for 循环本质上就是通过不断调用 next() 函数实现的

# 对可迭代对象进行迭代
for x in [1, 2]:
print(x)

# 等价实现

# 首先获得迭代器对象:
it = iter([1, 2])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
print(x)
except StopIteration:
# 遇到StopIteration就退出循环
break

这里总结一下 python 中的迭代器对象:

  1. 容器对象使用 iter() 封装后,如 list, tuple, set, dict, str 的实例对象经过 iter()封装;
  2. 文件对象;
  3. 类中定义了 __iter__()__next__() 的实例对象;
  4. 生成器对象。

自定义迭代器类,以打印斐波那契数列为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from itertools import islice, count, cycle

class Fibonacci:
# 无限序列
def __init__(self):
self.a = 0
self.b = 1

def __iter__(self):
return self

def __next__(self):
self.a, self.b = self.b, self.a + self.b
return self.a

# 获取前10项,及前10项和
f = Fibonacci()
list(islice(f, 0, 10)) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

f = Fibonacci()
sum(islice(f, 0, 10)) # 143

生成器

生成器(generator)是一种特殊的迭代器,它可以直接通过 yield 关键字把函数转化为生成器。如下定义的函数就是一个生成器,同样可以使用 next() 函数范围元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def FibonacciGenerator():
a, b = 0, 1
while True:
a, b = b, a + b
yield a

# 使用 next() 获取元素
f = FibonacciGenerator()
next(f) # 1
next(f) # 1
next(f) # 2

f = FibonacciGenerator()
list(islice(f, 0, 10)) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]


f = FibonacciGenerator()
sum(islice(f, 0, 10)) # 143

可以使用如下方法判断一个对象是不是生成器:

1
2
3
4
5
from collections.abc import Generator

isinstance(f, Generator) # True
isinstance(f, Iterator) # True # 生成器同时也是迭代器
isinstance(f, Iterable) # True # 生成器同时也是可迭代对象

这里总结一下 python 中的生成器对象:

  1. 使用yield定义生成器函数;
  2. 列表生成器或生成器表达式 (generator expression)。

列表生成器是如下的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
g = (x ** 2 for x in range(3))
list(g) # [0, 1, 4]

g = (x ** 2 for x in range(3))
sum(g) # 5

g = (x ** 2 for x in range(3))
for i in g:
print(i) # 0 1 4

g = (x ** 2 for x in range(3))
isinstance(g, Generator) # True
isinstance(g, Iterator) # True
isinstance(g, Iterable) # True

注意:这里不要写成 g = [x ** 2 for x in range(3)],此时 g 是列表,它只是可迭代对象,不是迭代器,更不是生成器

当程序遇到yield关键字时,这个生成器函数就返回了,直到再次执行了next()函数,它就会从上次函数返回的执行点继续执行,即yield退出时保存了函数执行的位置、变量等信息,再次执行时,就从这个yield退出的地方继续往下执行。

参考链接

  1. 一文彻底搞懂Python可迭代(Iterable)、迭代器(Iterator)和生成器(Generator)的概念
  2. Python迭代器与可迭代对象的区别与联系
  3. 迭代
  4. 迭代器
  5. 生成器
  6. 理解Python可迭代对象、迭代器、生成器