Python之迭代器、生成器
一、迭代器
1、1:什么是可迭代对象?
字符串、列表、元组、字典、集合都是可以被for循环便利的,说明他们都是可迭代的
我们来怎么证明这一点:
from collections import Iterable l = [1,2,3,4] t = (1,2,3,4) d = {1:2,3:4} s = {1,2,3,4} print(isinstance(l,Iterable)) print(isinstance(t,Iterable)) print(isinstance(d,Iterable)) print(isinstance(s,Iterable))
True
True
True
True
1、2:可迭代协议
我们现在是从结果分析原因,能被for循环的就是 "可迭代的",但是如果正想着,for循环怎么指定谁是可迭代的呢?
假如我们字节写了一个数据类型,希望这个数据类型里面的东西也可以使用for循环被一个一个的取出来,那我们必须满足for循环的要求,这个要求就叫做 "协议"
可以被迭代要满足的要求就叫做可迭代协议,可迭代协议的定义非常简单,就是内部实现了__iter__方法
接下来我们就来验证一下:
print(dir([1,2])) print(dir((2,3))) print(dir({1:2})) print(dir({1,2}))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
总结一下我们现在所知道的:可以被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法
那__iter__方法做了什么事情呢?
可迭代的:内部必须有一个__iter__方法
1、3:迭代器
什么叫做迭代器?迭代器英文意思是iterator
l = [1,2,3,4] #将可迭代的转换成迭代器
l_iter = l.__iter__() item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item)
1
2
3
4
迭代器遵守迭代器协议:必须用于__iter__方法和__next__方法
for循环,能便利一个可迭代对象,它的内部到底进行了什么?
将可迭代对象转换成迭代器(可迭代对象.__iter__)
内部使用__next__方法,一个一个取值
加了异常处理功能,取值到底后自动停止
用while循环模拟for循环:
l = [1,2,3,4] l_iter = l.__iter__() while True: try: item = l_iter.__next__() print(item) except StopIteration: break
1
2
3
4
1、4:为什么要有for循环
基于上面讲的列表这一大堆遍历方式,聪明的你立马看出了问题,于是你不知死活的大声喊道,这不是逗我玩呢吗,有了下标的访问方式,我们可以这也遍历一个列表
l = [1,2,3,4] index = 0 while index < len(l): print(l[index]) index += 1
没错,序列类型字符串,列表,元组都有下标,你用上述的方式访问,prefect!
但是你可曾想过非序列类型像字典,集合,文件对象的感受,所以for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了,而且你看到的效果也确实如此,这就是无所不能的for循环,最重要的一点,转化成迭代器,在循环时,同一时刻在内存中只出现一条数据,极大限度的节省了内存~
二,生成器
1、初识生成器
我们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象通过执行__iter__方法得到的,迭代器有的好处是可以节省内存
如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器
Python中提供的生成器:
1、生成器函数:常规函数定义,但是,使用yield语句而不是使用return语句放回结果
yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
2、生成器表达式:类似于列表推导,但是,生成器返回按需要产生结果的一个对象,而不是一次构建一个结果列表
生成器:Generator:
本质:迭代器(自带了__iter__ 和 __next__ 方法,不需要我们去实现)
特点:惰性运算,开发者自定义
2、生成器函数
一个包含yield关键字的函数就是一个生成器函数,yield可以为我们从函数中返回值,但是yield又不同于return,return执行意味着结束程序,调用生成器函数不会得到返回的具体值,而是得到一个可迭代的对象,每一次获取这个可迭代的值,就能推动函数的执行,获取新的返回值,直到函数结束执行
import time def genration_fun1(): a = 1
print("现在定义了变量a") yield a b = 2
print("现在定义了变量b") yield b g1 = genration_fun1() #打印g1可以发现g1就是一个生成器
print('g1',g1) #分割线
print('_'*20) print(next(g1)) #sleep一秒看清执行过程
time.sleep(1) print(next(g1))
g1 <generator object genration_fun1 at 0x0000000001E8A200>
____________________ 现在定义了变量a 1 现在定义了变量b 2
生成器有什么好处呢?就是不会一下子在内存中生成太多数据
假如我想让工厂给学生做校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,然后再去生产,我可以一件一件的要,也可以根据学生一批一批的找工厂拿。
而不能是一说要生产2000000件衣服,工厂就先去做生产2000000件衣服,等回来做好了,学生都毕业了...
def produce(): """生成衣服"""
for i in range(200000000): yield "生产了 %s 件衣服" % i product_g = produce() print(product_g.__next__()) print(product_g.__next__()) print(product_g.__next__()) num = 0 for i in product_g: print(i) num += 1
if num == 5: break
生产了 0 件衣服 生产了 1 件衣服 生产了 2 件衣服 生产了 3 件衣服 生产了 4 件衣服 生产了 5 件衣服 生产了 6 件衣服 生产了 7 件衣服
3、send
#send 获取下一个值的效果和next基本一致 #只是在获取下一个值的时候,给上一yield的位置传递一个数据 #使用send的注意事项
# 第一次使用生成器的时候 是用next获取下一个值
# 最后一个yield不能接受外部的值
def generator(): print(123) content = yield 1
print('========',content) print(456) yield 2 g = generator() ret = g.__next__() print("***",ret) #send的效果和next一样
ret = g.send("hello") print("****",ret)
123
*** 1
======== hello 456
**** 2
三、列表推导式和生成器表达式
l = [i for i in range(10)] print(l) l1 = ('选项 %s' % i for i in range(10)) print(l1) for i in l1: print(i,end=" ")
1、把列表解析的[]换成()得到的就是生成器表达式
2、列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3、Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
sum = sum(x**2 for x in range(4)) print(sum)
14