一 可迭代对象,迭代器和生成器
| 可迭代对象 | 迭代器 | 生成器 |
定义 | 可迭代 (Iterable):指某个容器类型数据可被for循环遍历获取内部所有成员。那么这个类型数据就称之为可迭代。 对象:也叫实例(Instance),可以理解为是一个记录和管理数据的容器,它的成员是属性(变量,属性的值就是数据)和方法(method, 就是操作该对象以及对象内部数据的函数)。 从使用角度来说,能被for循环遍历输出内部每一个成员的都是可迭代对象。 从语法形式来说,能调用 | 迭代器:增强版的可迭代对象。实现了迭代器协议的对象。即任何一个对象,只要内部实现了 ->迭代器一定是可迭代对象,可迭代对象不一定是迭代器 |
|
实例 | 判断一个数据是否是可迭代对象 set_data = {'a',2,'c','b'} |
# 迭代器中所有值都被提取完成后,再次取值,python会以抛出一个StopIteration异常告诉我们,没有值了。这并不代表错误发生,而是一种迭代完成的标志,防止出现无限循环。 #迭代器的特性就是能够调用 |
# 每次调用生成器函数,都会产生一个新的生成器对象。 |
比较 | 优点:
缺点:
| 优点:
缺点:
| 同迭代器。生成器中yield关键字的特征是暂停函数执行,所以可以让多个任务程序(函数)交替执行。这也是网络编程与并发编程里面的协程的实现基本原理,和异步编程里面协程基础 |
关系 | 可迭代对象包含了迭代器和生成器 |
注:
1 for循环的本质
x = 可迭代对象
# for循环的形式:
for item in x:
print(item)
for 循环首先会调用可迭代对象data内的__iter__
方法返回一个迭代器,然后再调用这个迭代器的__next__
方法将取到的值赋给item,即for后面定义的变量item(item经过每次for循环调用next()时都会赋值了一遍)。循环一次,调用一次next方法,直至遇到StopIteration异常,for循环内部捕获并处理该异常后结束迭代过程。
区别于while循环模拟遍历过程(while比for循环要单调一点,仅仅是重复)
data = [10,20,30,40]
# 1. 先执行iter方法,把可迭代对象转换成迭代器
iter_data = iter(data)
while True:
try: #异常处理语句,可以让我们识别代码异常而不会导致代码因为异常出错而终止
# 2. 每次循环,通过next取值,付给item
item = next(iter_data)
print(item)
except StopIteration:
break # 此处,退出循环
2 查看一个对象是否是可迭代对象或迭代器
from collections.abc import Iterable, Iterator
data = [1, 2, 3, 4]
print(isinstance(data, Iterable)) # True # 查看是不是可迭代对象
print(isinstance(data, Iterator)) # False # 查看是不是迭代器
# 在 python中容器数据类型都是可迭代对象,但是并非迭代器,那么我们就可以进行装换。[高阶函数(filter, map..)的返回值是一个迭代器]
# 方式1: print(isinstance(data.__iter__(), Iterator)) # True # 方式2: print(isinstance(iter(data), Iterator)) # True #isinstance 是面向对象里面的内容,表示判断当前变量是否属于某个类型。区别于type,用于基本函数类型
3 生成器实例补充
"""
next 使用生成器过程中,仅仅只能接受yield暂停时返回的数据
send 使用生成器过程中,不仅可以接受yield暂停时返回的数据,还可以在send()的小括号把数据从外界传递到生成器函数内部(传参)给上一个yield(也就是暂停出)接收到
"""
1 def gen2():
key = 0
print(">>>>> 嘟嘟,开车了")
while True:
food = yield "第%s次" % key
print(f"内部接收到了{food}")
key += 1
# 判断当前文件是否作为主程序入口来运行,
# 如果__name__魔术变量的值为"__main__"则表示当前文件是主执行文件
# 如果__name__魔术变量的值为文件名,则表示当前文件不是主执行文件
if __name__ == "__main__":
g2 = gen2()
# 在循环之前,先执行一遍send的作用是为了预激活生成器,
# 让生成器内部执行到第一个yield位置,否则代码没有暂停的话,就无法通过send传递数据给内部的yield
ret = g2.send(None)
print(ret)
for item in ["苹果","芒果"]:
res = g2.send(item)
print(f"res={res}")
输出:
嘟嘟,开车了
第0次
内部接收到了苹果
res=第1次
内部接收到了芒果
res=第2次
2 yield from可以将一个可迭代对象变成一个迭代器返回,也可以用于多个生成器之间进行嵌套调用
def gen1():
a = 0
while True:
# print("+++++++")
a = yield a**2
def gen2(gen):
# a = 0
# while True:
# # print("+++++++")
# a = yield a ** 2
yield from gen # 相当于一次性声明了多个yield
if __name__ == '__main__':
g1 = gen1()
g2 = gen2(g1)
g2.send(None)#在一个生成器函数未启动之前,是不能传递数值进去。必须先传递一个None进去或者调用一次next()方法,才能进行传值操作
for i in range(5):
# print(">>>> %s" % i)
print(g2.send(i))
输出:0 1 4 6 9 16
3 用生成器实现斐波那契
def fib(max):
n, a, b = 0, 0, 1 # n表示当前第几项,a表示第一项的值,b表示第二项的值
while n <= max:
yield a
a, b = b, a + b
n = n + 1
g1 = fib(10)
for i in g1:#生成器为特殊的迭代器,可遍历可调用next方法
print(i)#输出a的值
# 0 1 1 2 3 5 8 13
二 推导式
推导式,实际上就是一种代码简写方式。是通过一行代码完成一个或多个循环和判断,并遍历出一系列数据的编写代码方式
语法:
成员 for 循环 ... if 判断 ...
推导式种类
其中,列表和字典最常用
# 1. 列表推导式,结果是一个列表
[item for item in Iterable]
# 2. 字典推导式,结果是一个字典
{a:b for a,b in iterable.items()}
# 3. 集合推导式,结果是一个集合
{item for item in Iterable}
# 4. 生成器表达式,结果是一个生成器
(item for item in Iterable)
实例1 列"""
推导式里面可以存在1-多个for循环,也可以有0-多个判断,
但是判断和循环需要根据代码嵌套顺序来缩写
"""
"""1. 找出1~10之间的偶数"""
# ret = []
# for num in range(1, 11):
# if num % 2 == 0:
# ret.append(num)
# print(ret)
# 上面的这种结构代码就可以使用推导式进行简写
# ret = [num for num in range(1, 11) if num % 2 == 0]
# print(ret)
2 字典推导式
"""1. 统计列表中各个字符出现的次数"""
# data = ["A", "B", "C", "A", "C", "A"]
# # ret = {}
# # for item in data:
# # ret[item] = data.count(item)
#
# 上面的这种结构代码就可以使用推导式进行简写
# ret = {item: data.count(item) for item in data}
# print(ret)
3 集合推导式
"""1. 计算列表中每个值的平方,自带去重功能"""
# data = [1, -1, 2, -3, 3]
# # ret = set()
# # for num in data:
# # ret.add(num**2)
# 上面的这种结构代码就可以使用推导式进行简写
# ret = {num**2 for num in data}
# print(ret)
4 生成器推导式
获取1-10整数的平方
"""上面的代码实际上是以下代码的简写"""
def gen():
for i in range(1, 11):
yield i**2
n = gen()
for i in n:
print(i)# 上面的这种结构代码就可以使用推导式进行简写
# ret = (i**2 for i in range(1, 11))
# print(ret)