一、生成器函数进阶
1、最后一个yield后的代码
先看示例:
def generator(): print(123) yield 'a' print(456) yield 'b' print(789) g = generator() print(next(g)) print("-------我是分割线-------") print(next(g)) print("-------我是分割线-------") print(next(g)) print("-------我是分割线-------")
得到的结果:
上述示例中,生成器函数generator中在最后一个yield后面还有代码,在取完最后一个值后再次执行next(),可以看到后面的“789”依然输出了,但是函数执行完还是找不到下一个yield,就报错了。
同样的,for循环取值也是可以将生成器执行完的,for循环也是以报错结束,只有在函数找不到最后一个yield的时候才会报错。
如果真的在返回最后一个值后面还需要有其他代码,可以在最后再添加一个yield 空。
2、send
生成器.send()与next()一样,可以获取生成器中的下一个值,不同的是,send可以向生成器内部传值。例:
1 def generator(): 2 content = yield 1 # 注意写法 3 print("生成器内部:", content) 4 yield 2 5 6 7 g = generator() 8 ret = next(g) # 生成器的第一个值必须用next获取 9 print("获取的第一个值:", ret) 10 ret = g.send("哈哈") # 使用send向内部传值 11 print("获取的第二个值:", ret)
结果:
可以看到,在生成器内部content成功接收到了外部传的值,而且同时,使用send也同样可以获取到生成器的下一个值。
send是在获取下一个值的时候,给上一个yield的位置传递一个数据
使用send的注意事项:
1、第一次使用生成器的时候,只能使用next获取下一个值而不能使用send
2、最后一个yield不能接收外部的值,因为后面已经没有代码了,所以传值也没有意义了
send其实就是实现了一个内外互传值的效果,因为每次yield相当于一次return,以yield分割,每部分就是一个函数,所以函数理应有接收参数的功能。
动态平均值
像射箭、打靶,都会有一个平均值的显示,用以标示当前选手已经打靶的平均环数,这个平均值是随着打靶的轮数每次重新计算的,现在用程序来写一个动态平均值的问题,就需要用到send方法了。
def dynamic_avg(): sum = 0 count = 0 avg = 0 while True: new_value = yield avg # 传出当前平均值,接收新的值 sum += new_value count += 1 avg = sum / count avg_g = dynamic_avg() first = next(avg_g) # 过滤掉第一个值 while True: value = input("请输入新的值:") after = avg_g.send(int(value)) print("当前的平均值为:", after)
结果:
上面的生成器已经实现了动态平均值的基本功能,但是存在一点不完美,第一个值并不是我们需要的,而且第一个值必须要使用next获取,我们希望如果这是我们写给用户的函数,用户能用最简单的方式去调用,比如只需要使用send就可以正常使用这个生成器。因此,我们可以对上面的函数进行改造,这就需要用到装饰器:
1 def init(func): 2 """ 3 这是一个装饰器,对生成器函数进行初始化,过滤掉第一个值(预激协程的装饰器) 4 :param func: 生成器函数 5 :return: inner内部函数 6 """ 7 def inner(*args, **kwargs): 8 g = func() 9 next(g) # 在装饰器内部完成第一次调用 10 return g 11 return inner 12 13 14 @init 15 def dynamic_avg(): 16 sum = 0 17 count = 0 18 avg = 0 19 while True: 20 new_value = yield avg 21 sum += new_value 22 count += 1 23 avg = sum / count 24 25 26 avg_g = dynamic_avg() 27 # first = next(avg_g) 不再需要使用next过滤掉第一个值 28 while True: 29 value = input("请输入新的值:") 30 after = avg_g.send(int(value)) 31 print("当前的平均值为:", after)
拓展内容(模拟实现linux中的grep):
import os def init(func): def wrapper(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return wrapper @init def list_files(target): while 1: dir_to_search=yield for top_dir,dir,files in os.walk(dir_to_search): for file in files: target.send(os.path.join(top_dir,file)) @init def opener(target): while 1: file=yield fn=open(file) target.send((file,fn)) @init def cat(target): while 1: file,fn=yield for line in fn: target.send((file,line)) @init def grep(pattern,target): while 1: file,line=yield if pattern in line: target.send(file) @init def printer(): while 1: file=yield if file: print(file) g=list_files(opener(cat(grep('python',printer())))) g.send('/test1') 协程应用:grep -rl /dir tail&grep
这是一个层层递进式的查找文件内容的程序,装饰器实现了预激活,list_files获取文件夹下所有文件,然后将文件传给opener,opener打开文件然后将文件句柄传给cat,cat遍历文件内容,将文件的每行内容传给grep,grep进行与查找内容的比对,包含查找内容的就将文件名传给printer,printer进行打印,实现了一个利用生成器函数的完整的查找文件程序,各函数分工明确。当然,其实也可以不分这么多函数,这个更多的是深入了解生成器函数,同时也模仿了linux中的许多命令的功能。
3、yield from
yield from的中文讲解很少 , python官网是这样解释的
PEP 380 adds the yield from expression, allowing a generator to delegate part of its operations to another generator. This allows a section of code containing yield to be factored out and placed in another generator. Additionally, the subgenerator is allowed to return with a value, and the value is made available to the delegating generator.
大意是说 yield from 表达式允许一个生成器代理另一个生成器, 这样就允许生成器被替换为另一个生成器, 子生成器允许返回值。例:
def generator(): s = 'abcdef' yield from s # s返回的迭代器来代理这个生成器 g = generator() print(next(g)) print(next(g)) print(next(g))
二、列表推导式
[表达式 for 变量 in 列表] 或者 [表达式 for 变量 in 列表 if 条件]
第一种写法举例: [x for x in range(5)] 、 [x*x for x in range(5)] 、 ["鸡蛋%s"%x for x in range(5)] 、 [(i, j) for i, j in enumerate(range(5), 10)] 、
[i for i in enumerate(range(5), 10)] 、 [(x, y) for x in range(10) for y in range(5)]
第二种写法举例: [x for x in range(10) if x % 2 == 0] ,其他表达式写法同第一种。
所以列表表达式前面的表达式可以任意写,后面的其实就是循环体加条件。
三、生成器表达式
生成器表达式与列表表达式的写法完全一致,只是括号为小括号“()”,即 (表达式 for 变量 in 列表) 或者 (表达式 for 变量 in 列表 if 条件)
注意:小括号不能省略
例:
# 打印10以内的偶数的平方 g = (x**2 for x in range(10) if x % 2 == 0) for i in g: print(i)
结果:
生成器表达式与列表推导式的区别:
1、括号不一样
2、返回的值不一样,生成器表达式不管结果多大,都占用很小的内存,而列表推导式还是将所有的值一次性取出,占用内存会随结果的大小改变。
四、其他推导式
1、字典推导式
例一:将一个字典的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)
2、集合推导式
例:计算列表中每个值的平方,自带去重功能
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]]
其中第二题,题目原意是
[(x, y) for x in range(6) if x % 2 == 0 for y in range(6) if y % 2 == 1]
但因为自己一开始理解错误,以为是有一个列表,从中取出符合条件的元祖列表,写成了:
lst = [(1, 4), (3, 0), (2, 5), (1, 9), (3, 4), (8, 3)] [i for i in lst if 0 <= i[0] <= 5 and 0 <= i[1] <=5 and i[0] % 2 ==0 and i[1] % 2 == 1]
这个结果倒是与我想的一致,但是pycharm在中间代码 0 <= i[0] <= 5 and 0 <= i[1] <=5 and i[0] % 2 ==0 一直提示我可以简化,但是我一直看不出哪里可以简化,就让pycharm自动简化了,结果变成了 5 >= i[0] >= 0 == i[0] % 2 and 0 <= i[1] <= 5 ,这个写法至少在我所知的语言里是没有的,之前只知道可以把大于小于写在一起,不知道可以一直联等下去,发现python真的是将语言高度精简了,上面这个问题也可以改成 5 >= i[0] >= 0 <= i[1] <= 5 and i[0] % 2 == 0 ,感觉又发现了新大陆。