python之路-----迭代器 和生成器
迭代器
迭代器 和 可迭代对象
for 循环的本质就是一个python给我们提供的一个便捷迭代器
for循环的本质就是在内部调用了__next__方法才能取到一个一个的值。
#for 循环的对象可以是一个迭代对象 也可以是一个迭代器 #for[1,2,3] # [1,2,3].__iter__() # __next__()
能被for循环的就是可迭代的
可迭代协议
-- 可迭代协议
就是数据类型和python解释器定下来的协议
'.__iter__'这个方法导致了一个数据类型的可迭代
只要包含了'双下iter'方法的数据类型就是可迭代的
在python里 你学过的所有的可以被for循环的 基本数据类型 都是可迭代的 而不是迭代器
迭代器----iterator
迭代器协议
迭代器必须要满足两点才可以叫做可迭代器
: 内部实现了__iter__和__next__方法
迭代器的本质 就是for循环调用的底层内置函数的运用
可迭代对象:字符串、列表、元组、字典、集合都是可迭代的对象
.............................
Iterable -- 可迭代的 形容词
iterator -- 迭代器 名词
....................................
迭代器包含可迭代对象
迭代器=可迭代对象.__iter__()
执行可迭代的Iterable方法就得到一个iterator迭代器
为什么要用迭代器?
第一个功能,能够对python中的基本数据类型进行统一的遍历,不需要关心每一个值是什么
第二个功能,他可以节省内存
第三,思想--惰性运算 只有在你要的时候__next__时才会执行调用
重要点''''只能迭代出当前和下一个数据 不能反向迭代 ,
文件句柄就是一个迭代器 f=open('','')
range 就是一个可迭代的对象 他也是为了节省内存存在的
通过代码来理解
''' dir([1,2].__iter__())是列表迭代器中实现的所有方法,dir([1,2])是列表中实现的所有方法,都是以列表的形式返回给我们的,为了看的更清楚,我们分别把他们转换成集合, 然后取差集。 ''' #print(dir([1,2].__iter__())) #print(dir([1,2])) print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 结果: {'__length_hint__', '__next__', '__setstate__'}
迭代器中的三个方法的作用
iter_l = [1,2,3,4,5,6].__iter__() #获取迭代器中元素的长度 print(iter_l.__length_hint__()) #根据索引值指定从哪里开始迭代 print('*',iter_l.__setstate__(4)) #一个一个的取值 print('**',iter_l.__next__()) print('***',iter_l.__next__())
在for循环中,就是在内部调用了__next__方法才能取到一个一个的值,,但是取不到值的时候会报错
经典 一眼就能看出来 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) item = l_iter.__next__() print(item)经典
内置函数:next和iter方法
print('__next__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__next__ print('__iter__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__next__ from collections import Iterator print(isinstance(range(100000000),Iterator)) #验证range执行之后得到的结果不是一个迭代器 range函数的返回值是一个可迭代对象
生成器--本质上就是一个'自己写的可实现迭代器功能的东西'
目的是为了节省内存 ,不会在内存中生成太多数据
生成器 里面的值是有限的 只能被顺序取用一次, 取完就没有了
生成器里面的值只在调用的时候才取值
1.生成器函数:只要包含yield关键字的函数就是一个生成器函数
使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
import time def genrator_fun1(): a = 1 print('现在定义了a变量') yield a b = 2 print('现在又定义了b变量') yield b g1 = genrator_fun1() print('g1 : ',g1) #打印g1可以发现g1就是一个生成器 print('-'*20) #我是华丽的分割线 print(next(g1)) time.sleep(1) #sleep一秒看清执行过程 print(next(g1)) 初识生成器函数
#初识生成器二 def produce(): """生产衣服""" for i in range(2000000): 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: #要一批衣服,比如5件 print(i) num +=1 if num == 5: break #到这里我们找工厂拿了8件衣服,我一共让我的生产函数(也就是produce生成器函数)生产2000000件衣服。 #剩下的还有很多衣服,我们可以一直拿,也可以放着等想拿的时候再拿 初识生成器二
1.3 生成器Generator的本质:开发者自定义的迭代器 ..惰性运算 ...节省内存..
1.4生成器函数: 一个包含yield关键字的函数就是一个 生成器函数, yield可以为我们从函数中返回值,但其又不同于return,return意味着程序结束 而其是程序停在那里 需要调用的时候再调用 就可以继续运行
代码体现生成器不占内存
import time def genrator_fun1(): a = 1 print('现在定义了a变量') yield a b = 2 print('现在又定义了b变量') yield b g1 = genrator_fun1() print('g1 : ',g1) #打印g1可以发现g1就是一个生成器 print('-'*20) #我是华丽的分割线 print(next(g1)) time.sleep(1) #sleep一秒看清执行过程 print(next(g1)) 初识生成器函数
监控文件这点自己不是太懂记得问一下
import time def tail(filename): f = open(filename) f.seek(0, 2) #从文件末尾算起 while True: line = f.readline() # 读取文件中新的文本行 if not line: time.sleep(0.1) continue yield line tail_g = tail('tmp') for line in tail_g: print(line) 生成器监听文件输入的例子
小结和代码练习
#可迭代对象 内部有__iter__ #迭代器 内部有__iter__ __next__ #生成器 就是迭代器 #自己写的迭代器 #生成器函数 yield /yield from #生成器表达式 def generator_func(): #生成器函数 print(123) yield 'aaa' #return print(456) yield 'bbb' def get_clothing(): for cloth in range(1,2000000): yield '第%s件衣服'%cloth import time def tail(filename): f = open(filename,encoding='utf-8') f.seek(0,2) #将文件的光标移到最后 while True: line = f.readline() if not line: time.sleep(0.1) continue yield line tail_g = tail('demo') for line in tail_g: print(line,end='')
yield form 就相当于for i in 然后yield 返回值
def func2(): yield from [1,2,3] yield from 'ABC' g = func2() for i in g: print(i) # 生成器 # 处理文件,从一个文件中找到包含自己要查找的值的那一行,并打印出来 # python
生成器函数进阶与关键字send的用法
生成器函数 用代码 来具体表现一下 他的惰性 和 取值
# def func(): # yield 1 # yield 2 # yield 3 # # g=func() # for i in g: # print(i) # print(list(g))
send :文字面意思就是发送 , 在生成器函数中表达的就是传值
send(进去的值 )=yield+返回值
代码表达就是:
def func():
print(123)
value=yield 1
print(value)
print(456)
yield '***'+value+'***'
g=func()
print(g.__next__())
print(g.send('aaa'))
#执行结果会拿到 123,1,aaa,456,***aaa***
send 存在的作用就是 往生成器函数中发送一个值
第一小段练习 def func(): print(123) value=yield 1 yield value g=func() g.__next__() print(g.send('aaa')) 第二小段练习 def func(): print('*') value=yield 1 print('**',value) yield 2 g=func() print(g.__next__()) print(g.send('aaa')) 第三小段练习 def func(): print('*') value=yield 1 print('**',value) v=yield 2 print(v) yield 3 g=func() print(g.__next__()) print(g.send('aaa')) print(g.send('bbb')) 第四小段练习 def func(): print(1) yield 2 print(3) value=yield 4 print(5) yield value g=func() print(g.__next__()) print(g.send('100')) print(g.__next__()) 虽然在调用阶段 send进去一个值 但是send进去的值没有yield来接收 所以返回的是一个空 第四小段结果是 1 2 3 4 5 none
第五小段练习
def func():
print(1)
a=yield 2
yield a
print(3)
value=yield 4
print(5)
yield value
g=func()
print(g.__next__())
a=g.send('100')
print(a)
print(g.__next__())
得到的结果是 1 2 100 3 4
如果注释掉 a=yield 2 那就是返回 1 2 3 4 5 none
代码体现send关键字在实际中的书写:
计算移动平均值:总值/个数 (他们不是一个固定值)
def averager(): total = 0.0 count = 0 average = 0 while True: term = yield average #term依次=10=30=5 total += term count += 1 average = total/count g_avg = averager() next(g_avg) print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5)) 计算移动平均值(1)
加装饰器的计算平均值(2)_预激活
def init(func): #在调用被装饰生成器函数的时候首先用next激活生成器 def inner(*args,**kwargs): g = func(*args,**kwargs) next(g) return g return inner @init def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() # next(g_avg) 在装饰器中执行了next方法 print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5)) 计算移动平均值(2)_预激协程的装饰器
生成器进阶小结
send :首先send和next工作的起止位置是完全相同的
send可以把一个值作为信号量传递到函数中去
在生成器伊始,只能用next
只要用send传递参数的时候,必须在生成器中还有一个未被返回的yield
在平时生活当中 没有必须的点 面试时用到
列表推导式和生成器表达式
生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
#***由于*哥的强势加盟很快走上了上市之路,***思来想去决定下几个鸡蛋来报答峰哥 egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析 #*哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下 laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式 print(laomuji) print(next(laomuji)) #next本质就是调用__next__ print(laomuji.__next__()) print(next(laomuji)) *哥与****的故事
(总结的知识点 希望大家能看懂 )
#列表解析 sum([i for i in range(100000000)])#内存占用大,机器容易卡死 #生成器表达式 sum(i for i in range(100000000))#几乎不占内存
列表推导式的目的是为了 : 简化代码
列表推导式: print([i*i for i in[1,3,9]] )#结果必须是列表[1,9,81]
for前面就是新列表里面的值 和 一些附加条件 , 也可以在末尾加一些if条件判断
问1:用range取7以内能被2整除的数字 , 一列表推导式表现出来
print([i//2 for i in range(0,7,2)])
问2:加上字符串拼接 : print(['egg%s'%i for i in range(10)])
问3:加上一些条件判断: print([i for i in range(30) if i % 3 is 0])
问4: 找到嵌套列表中名字含有两个‘e’的所有名字
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] print([name for lst in names for name in lst if name.count('e') >= 2]) # 注意遍历顺序,这是实现的关键 for i in (name for lst in names for name in lst if name.count('e') >= 2): print(i)
小结: 写代码时注意点:
写代码尽量多的让列表推导式[]--默认变成生成式表达式()
让推导式简化你的操作,增强你的代码可读性
如果推导式过于复杂,应该转换成普通的python代码
所有的列表推导式都可以转化成生成器表达式 ,在你的代码中尽量多的用到生成器表达式
禁忌: 在代码里多层嵌套的for循环是禁忌--会大幅度增加代码复杂程度
生成器表达式 -- 节省内存 , 简化代码 就是把 列表推导式的[]去掉
laomuji=('egg%d'%i for i in range(10))
print(laomuji)
1.__next__()
2.for
for i in laomuji:
print(i)
laomuji 是一个可迭代对象 不是一个迭代器/生成器
总结:
1.把列表解析的[]换成()得到的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
sum(x ** 2 for x in xrange(4))
而不用多此一举的先构造一个列表:
sum([x ** 2 for x in xrange(4)])
字典推导式
例一:将一个字典的key和value对调
mcase = {'a': 10, 'b': 34} mcase_frequency = {mcase[k]: k for k in mcase} print(mcase_frequency)
例二:合并大小写对应的value值,将k统一成小写
mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()} print(mcase_frequency)
集合推导式
例:计算列表中每个值的平方,自带去重功能
squared = {x**2 for x in [1, -1, 2]} print(squared) # Output: set([1, 4])
练习题:
例1: 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母
例2: 求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表
例3: 求M中3,6,9组成的列表M = [[1,2,3],[4,5,6],[7,8,9]]
1.[name.upper() for name in names if len(name)>3] 2.[(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 3. [row[2] for row in M]