11.1 容器,可迭代对象和迭代器
- 容器:对象的集合,如列表、元组、字典、集合
- 所有容器都是可迭代的(iterable)
- 迭代器(iterator)提供了next()方法,调用这个方法,要么得到容器的下一个对象,要么得到一个StopIteration的错误。next()可以一个不漏地一个一个拿到容器中的所有对象
11.2 生成器
11.2.1 生成器的概念:
- 通过列表解析我们可以生成一个列表来访问列表中的值,但如果我们仅仅需要访问前面几个元素,若采用列表解析的话,则其实生成完整的列表浪费了很多空间。所以,若列表元素能按照某种算法推算出来,我们就大可不必创建完整的list,一边循环一边计算,称为生成器generator
- 生成器是迭代器的一种,使用yield返回,每次调用yield会暂停
- 迭代器是一个有限集合,生成器则可以成为一个无限集,配合next(generator)生成下一个对象
11.2.2 生成器的作用
不同于一般的容器会包含所有计算要用到的对象,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快地处理前几个返回值;同时,生成器使得函数可以“保留现场”,当下一次执行该函数是从上一次结束的地方开始,而不是重新再来
11.2.3 生成器的使用
生成器是特殊的迭代器,在python的写法有:
- 使用小括号括起来,如(i for i in range(100000000)),即初始化了一个生成器
- 使用yield代替return,函数变为生成器;在外部配合next()函数生成下一个对象
示例1
以下以验证 ( 1 + 2 + 3 + . . . ) 2 (1+2+3+...)^2 (1+2+3+...)2 = 1 3 + 2 3 + 3 3 + . . . 1^3 + 2^3 + 3^3 + ... 13+23+33+...这个公式的代码为例,说明生成器的使用方法。
def generator(k):
i = 1
while True:
yield i ** k
i += 1
gen_1 = generator(1)
gen_3 = generator(3) # 传入k=3
print(gen_1)
print(gen_3)
def get_sum(n):
sum_1, sum_3 = 0, 0
for i in range(n): # 每一轮,两个生成器都生成下一个对象以供使用
next_1 = next(gen_1)
next_3 = next(gen_3)
print('next_1 = {}, next_3 = {}'.format(next_1, next_3))
sum_1 += next_1
sum_3 += next_3
print(sum_1 * sum_1, sum_3)
get_sum(8)
########## 输出 ##########
<generator object generator at 0x000001E70651C4F8>
<generator object generator at 0x000001E70651C390>
next_1 = 1, next_3 = 1
next_1 = 2, next_3 = 8
next_1 = 3, next_3 = 27
next_1 = 4, next_3 = 64
next_1 = 5, next_3 = 125
next_1 = 6, next_3 = 216
next_1 = 7, next_3 = 343
next_1 = 8, next_3 = 512
1296 1296
示例2
给定一个list和一个数字,返回该数字在list中的下标位置
常规写法:
def index_normal(L, target):
result = []
for i, num in enumerate(L):
if num == target:
result.append(i)
return result
print(index_normal([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2))
########## 输出 ##########
[2, 5, 9]
使用生成器的写法:
def index_generator(L, target):
for i, num in enumerate(L):
if num == target:
yield i
print(list(index_generator([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2)))
########## 输出 ##########
[2, 5, 9]
示例3
给定两个序列,判断第一个序列s是否为第二个序列t的子序列。
常规写法:利用两个指针i,j分别指向两个序列,依次遍历第二个序列,判断t[j] == s[i],若条件成立则两个指针后移一位,若不成立则继续遍历t序列;当i = = == == len(s)时,即遍历完序列s后,返回True
使用迭代器和生成器的写法:
def is_subsequence(a, b):
b = iter(b) # 使用迭代器
return all(i in b for i in a) # 使用生成器
print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5]))
########## 输出 ##########
True
False
仅用3行代码就完成了目标函数的定义。其中,(i in b) for i in a实际上使用了生成器;all()函数判断iterable对象的元素是否全为True
对于i in iterator的用法,举个例子就清楚了:
b = (i for i in range(5)) # b = iter([0,1,2,3,4])也一样
print(2 in b)
print(3 in b)
print(0 in b)
=========================
True
True
False
相比与第一种解法,使用生成器和迭代器使得在不知道序列的长度的前提下,也能优雅地进行遍历操作,不用操心控制越界报错的问题