10 迭代器和生成器
目录链接:https://www.jianshu.com/p/e1e201bea601
容器、 可迭代对象和迭代器
容器:容器是一系列元素的集合,str、list、set、dict、file、sockets对象都可以看作是容器,容器都可以被迭代(用在for,while等语句中),因此他们被称为可迭代对象。
可迭代对象:很多容器都是可迭代对象,此外还有更多的对象同样也是可迭代对象,比如处于打开状态的files,sockets等。但凡是可以返回一个迭代器的对象,都可称之为可迭代对象。
迭代器:一个带状态的对象,能在你调用next()方法时,返回容器中的下一个值。任何实现了iter()和next()方法的对象都是迭代器,iter返回迭代器自身,next返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。
x = [1, 2, 3]
y = iter(x)
z = iter(x)
print(next(y)) # 1
print(next(y)) # 2
print(next(z)) # 1
print(type(x)) # <class 'list'>
print(type(y)) # <class 'list_iterator'>
容器、 可迭代对象和迭代器01.png
x是一个可迭代对象,y和z是两个独立的迭代器。可迭代对象, 通过 iter() 函数返回一个迭代器, 再通过 next() 函数就可以实现遍历。
下面这段代码展示怎么判断一个对象是否可迭代,当然也可以用isinstance(obj, Iterable)
from collections import Iterable
def is_iterable(param):
try:
iter(param)
return True
except TypeError:
return False
params = [
1234,
'1234',
[1, 2, 3, 4],
set([1, 2, 3, 4]),
{1:1, 2:2, 3:3, 4:4},
(1, 2, 3, 4)
]
for param in params:
print('Method 1: {} is iterable? {}'.format(param, is_iterable(param)))
print("--------------------------------------------------------------------")
for param in params:
print('Method 2: {} is iterable? {}'.format(param, isinstance(param,Iterable)))
容器、 可迭代对象和迭代器01.png
生成器
生成器是一种特殊的迭代器。
一个函数只返回一次,但一个生成器能暂停执行并返回一个中间的结果(yiled关键字),当生成器的next()方法被调用时,它就又会从离开的地方继续运行,实现一边循环一边计算的机制,这种就称为生成器generator。
def generator(k):
i = 1
while True:
yield i ** k
i += 1
gen_1 = generator(1) # 返回一个生成器
gen_3 = generator(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)
生成器01.png
generator() 这个函数, 它返回了一个生成器。yield 关键词,函数运行到这里的时候, 程序会从这暂停, 然后跳出, 不过跳到哪里呢?答案是 next() 函数。 那么 i ** k 是干什么的呢?它其实成了 next() 函数的返回值。
每次 next(gen) 函数被调用的时候, 暂停的程序就又复活了, 从 yield 这里向下继续执。局部变量 i 并没有被清除掉,而是会继续累加。
这个生成器可以一直进行下去,事实上, 迭代器是一个有限集合, 生成器则可以成为一个无限集。只管调用next(), 生成器根据运算会自动生成新的元素, 然后返回。
例子
判断子序列:https://leetcode-cn.com/problems/is-subsequence/
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。[1, 3, 5] 是 [1, 2, 3, 4, 5] 的子序列, [1, 4, 3] 则
不是。
常规解法:
双指针,指针 i 指向 s 第一个字符,指针 j 指向 t 第一个字符。逐一判断 i 所指向的字符是否在 t 中存在。
如果 s[i] != t[j]:j = j + 1,继续比对下一个字符
如果 s[i] == t[j]:i = i + 1,j = j + 1,继续进行下一个 s[i] 的查找
def isSubsequence(s, t):
s_length = len(s)
t_length = len(t)
i = 0
j = 0
while i < s_length and j < t_length:
if s[i] == t[j]:
i += 1
j += 1
return i == s_length
print(isSubsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print(isSubsequence([1, 4, 3], [1, 2, 3, 4, 5]))
双指针.png
使用迭代器和生成器:
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]))
迭代器和生成器.png
先把这段代码扩展,一步一看
def is_subsequence(a, b):
b = iter(b) # 把列表 b 转化成了⼀个迭代器
print(b)
gen = (i for i in a) # 产生一个生成器, 这个生成器可以遍历对象 a
print(gen)
for i in gen:
print(i)
gen = ((i in b) for i in a)
print(gen)
for i in gen:
print(i)
return all(((i in b) for i in a))
print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print('--------------------------------------------')
print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5]))
迭代器和生成器.png
b = iter(b) , 把列表 b 转化成了一个迭代器, gen = (i for i in a) 产生一个生成器, 这个生成器可以遍历对象 a,能够输出 1, 3, 5。(i in b)可以联想到 for in 语句。
这里的 (i in b) , 大致等价于下面这段代码:
while True:
val = next(b)
if val == i:
yield True
巧妙地利用了成器的特性, next() 函数运行的时候, 保存了当前的指针。
那么((i in b) for i in a)可以理解为:遍历a中元素 i,并在b中查找元素 i 是否存在。由于next() 函数和yield 成器的特性,如果 b 存在 i 返回True并保存当前指针不存在返回False(下一次查找从保存的指针开始继续查找,直到a 或者 b 遍历结束),这些True或False产生一个迭代器。
最后的 all() 函数,判断一个迭代器的元素是否全部为 True, 如果是则返回 True, 否则就返回 False。
参考资料:
极客时间 Python核心技术与实战学习
Python核心技术与实战(极客时间)链接:
http://gk.link/a/103Sv
GitHub链接:
https://github.com/lichangke/LeetCode
个人Blog:
https://lichangke.github.io/
欢迎大家来一起交流学习