一 迭代器
1.1 认识迭代器
什么是迭代 什么是迭代器
迭代器 如何从列表、字典中取值的 index索引 ,key for循环 凡是可以使用for循环取值的都是可迭代的 可迭代协议 :内部含有__iter__方法的都是可迭代的 迭代器协议 :内部含有__iter__方法和__next__方法的都是迭代器
一个生成器 只能取一次
生成器在不找它要值的时候始终不执行
当他执行的时候,要以执行时候的所有变量值为准
l = [1,2,3] while True: lst = l.__iter__() ## iter 方法自带一个next方法,所以第一次可以取值输出,并取到下一个值保存 print(next(lst)) l = [1,2,3] lst = l.__iter__() ##当输出最后一个值的时候,仍旧执行next方法会报错 while True: print(next(lst)) l = [1,2,3] lst = l.__iter__() while True: try: print(next(lst)) # 测试错误 except StopIteration: # 当遇到报错 StopIteration 的时候,执行break,停止执行next方法。
break
什么是迭代器 迭代器 = iter(可迭代的),自带一个__next__方法
可迭代 最大优势是 节省内存
我们可以从range学习
from collections import Iterable,Iterator #可迭代的 迭代器 print(range(100000000)) print(isinstance(range(100000000),Iterable)) # True 是可迭代的 print(isinstance(range(100000000),Iterator)) # False 不是迭代器
py2 range 不管range多少 会生成一个列表 这个列表将用来存储所有的值
py3 range 不管range多少 都不会实际的生成任何一个值
迭代器的优势:
节省内存
取一个值就能进行接下来的计算 ,而不需要等到所有的值都计算出来才开始接下来的运算 —— 快
迭代器的特性:惰性运算
二 生成器
生成器,Genenator
2.1 什么是生成器
自己写的迭代器,就是一个生成器
写迭代器的两种机制:
生成器函数和生成器表达式
# 凡是带有yield的函数就是一个生成器函数 def func(): print('****') yield 1 print('^^^^') yield 2 # 记录当前所在的位置,等待下一次next来触发函数的状态
生成器函数的调用不会触发代码的执行,而是会返回一个生成器(迭代器)
想要生成器函数执行,需要用next
g = func() # g = 函数执行之后生成的一个生成器
print('---',g) # 函数执行不输出任何内容
print('--',next(g)) # 调用一次next,会执行一次取值,到下一次遇到yield停止 print('--',next(g))
调用两次next返回的结果:
**** -- 1 ^^^^ -- 2
2.1.2使用生成器监听文件输入
import time def listen_file(): with open('userinfo') as f: while True: line = f.readline() #使用readline监听文件,它不会自动结束 if line.strip(): yield line.strip() time.sleep(0.1) g = listen_file() for line in g: print(line)
# 使用mac的时候,需要用echo向文件追加内容测试。win手动输入 使用ctrl+s保存即可
2.1.3 send关键字
理解send
def func(): print(11111) ret1 = yield 1 print(22222,'ret1 :',ret1) ret2 = yield 2 print(33333,'ret2 :',ret2) yield 3 g = func() ret = next(g) # 第一次next,返回11111 和 yield 1 。并记录当前位置,等待下一个next来触发函数状态 print(ret) print(g.send('alex')) # 在执行next的过程中 传递一个参数 给生成器函数的内部,同时接收yield的返回值。 print(g.send('金老板')) ##接收send发送的参数的是上次次结束的yield。第一次执行next(g),后,返回yield 1。yield会记录当前所在位置,等待下一个next来触发函数的状态。当执行send(alex)的时候的时候,相当于又执行一个next,且yield 1首先执行,接收send的内容。 # send 不能在第一次next之前。第一次必须用next触发这个生成器,有一个激活的过程
send 不能用在生成器的第一步。必须先用next触发激活生成器
例子: 计算移动平均值
要求,每次给你一个数,都计算之前所有拿到的数的平均值。例如计算月度的天平均收入
def average(): sum_money = 0 day = 0 avg = 0 while True: money = yield avg sum_money += money day += 1 avg = sum_money/day g = average() print(g.__next__()) #第一步 先触发生成器,之后才能send print(g.send(20)) print(g.send(40)) print(g.send(60))
2.2 预激生成器
个人理解:生成器send的时候需要用next激活,才能使用send传值。这里用到的是装饰器来进行生成器函数的预激活
def init(func): def inner(*args,**kwargs): ret = func(*args,**kwargs) next(ret) # 预激活 return ret # 需要返回生成器的结果给send return inner @init def average(): sum_money = 0 day = 0 avg = 0 while True: money = yield avg sum_money += money day += 1 avg = sum_money/day # g = average() print(g.send(200)) print(g.send(300)) print(g.send(600))
2.3 生成器的使用
2.3.1 yield from
# yield from def generator_func(): for i in range(5): yield i for j in 'hello': yield j def generator_func(): yield from range(5) yield from 'hello' 以上两种写法完全是完全相同的
2.3.2 迭代器只能生成一次
我们看下这两段代码
# 定义函数 # # yield from def generator_func(): for i in range(5): yield i # 代码调用第一种方式 g = generator_func() while True: print(next(g)) #输出内容: 0 1 2 3 4 File "/Users/wph/PycharmProjects/learn/Day4/8.复习练习.py", line 194, in <module> print(next(g)) StopIteration # 代码调用第二种方式 while True: print(next(generator_func())) # 执行结果是一直输出 0
我们想一下,为什么会出现这样的情况呢?
来继续看下面的代码:
def generator_func(): for i in range(5): yield i g1 = generator_func() g2 = generator_func()
print(g1,g2)
#输出结果 <generator object generator_func at 0x104c2b0a0> <generator object generator_func at 0x104c2b0f8> 可以看到 g1和g2的内存地址是不一样的。也就是说每次调用generator_func,都会生成一个全新的生成器。那么每次从里面取值,都是会从头开始的。
2.3.3 生成器函数定义
生成器函数 是我们python程序员实现迭代器的一种手段
主要特种是 在函数中 含有 yield
调用一个生成器函数 不会执行这个函数中的代码,知识会获得一个生成器(迭代器)
只有从生成器中取值的时候,才会执行函数内部的代码。且每获取一个数据才执行得到这个数据的代码
获取数据的方式包括 next send 循环 数据类型的强制转换
yield返回值的简便方法,如果本身就是一个可迭代的,且要把可迭代数据中的每一个元素都返回,可以用 yield from
使用send的时候,在生成器创造出来自后要进行预激,这一步可以使用装饰器完成。
生成器的特点:节省内存 惰性计算
生成器用来解决内存问题和程序功能之间的解耦
2.3.4 生成器表达式
先了解一下列表推导式:
# 列表推导式 new_lst = [] for i in range(10): new_lst.append(i**2) print(new_lst) print([i**2 for i in range(10)]) # 求列表中的数对2取余 l = [1,2,3,-5,6,20,-7] print([i%2 for i in range(10)]) # 取出列表中的奇数 l = [1,2,3,-5,6,20,-7] print([num for num in l if num%2 == 1]) 30以内所有能被3整除的数 print([i for i in range(30) if i%3 ==0]) 30以内所有能被3整除的数的平方 print([i**2 for i in range(30) if i%3 ==0]) 找到嵌套列表中名字含有两个‘e’的所有名字 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] print([name for name_lst in names for name in name_lst if name.count('e') == 2])
生成器表达式
l = [1,2,3,-5,6,20,-7] 30以内所有能被3整除的数 l = [i for i in range(30) if i%3 ==0] # 列表推导式 排序的时候 g = (i for i in range(30) if i%3 ==0) # 生成器表达式 庞大数据量的时候 使用生成器表达式 #与列表推导式相比,只是把[]换成了() print(l) #结果是一个列表 print(g) ## 结果只是拿到一个生成器 <generator object <genexpr> at 0x1043100a0> for i in g:print(i) # 从这个生成器中取值
总结:
1. 把列表解析(列表推导式)的[]换成()得到的就是生成器表达式
2. 列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存。
3. Python不但使用迭代器协议,让for循环更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如,sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所有,我们可以直接这样计算一系列值的和
print(sum(x ** 2 for x in range(4)))
2.3.5 生成器面试题
# 面试题 请确认这段代码的打印内容 def demo(): for i in range(4): yield i g=demo() g1=(i for i in g) ### 生成器表达式 不执行! g2=(i for i in g1) print(list(g1)) #执行生成器表达式 #结果 [0,1,2,3] print(list(g2)) #结果 []
解析:因为g=demo()的使用调用生成器函数,g是一个生成器。g1 是一个生成器推导式,并不会执行。
当print从g1 中取值完成后,由于一个生成器只能取值一次。,g2取不到值了。
面试题2: 很蛋疼的,慢慢看吧 ,不信你能懂。。(重点在于 生成器是什么时候执行的)
# 判断下列代码输出内容 def add(n,i): return n+i def test(): for i in range(4): yield i g=test() for n in [1,3,10]: g=(add(n,i) for i in g) print(list(g))
输出内容
10 11 12 13
2.3.5 生成器准则
一个生成器 只能取值一次。
生成器在不找他要值的时候,始终不执行
当他执行的时候,要以执行时的所有变量值为准