Python生成器
生成器的定义:生成器它的本质就是迭代器
我们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象通过执行iter方法得到的,迭代器有的好处是可以节省内存。
如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器
在python中有以下几种方式来获取生成器
1.通过生成器函数
2.通过各种推到式来实现生成器
首先,我们先看一个很简单的函数:
def func(): print(11) return 22 ret = func() print(ret) # 运行结果: 11 22
我们只需要修改一个地方就可以把函数变成生成器 就是将函数中的return换成yield就是生成器
Python中提供的生成器:
1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器Generator:
本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)
特点:惰性运算,开发者自定义
定义生成器
def func(): print(11) yield 22 ret = func() print(ret) # 运行结果: <generator object func at 0x000001A575163888>
一个包含yield关键字的函数就是一个生成器函数。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))
print("111") yield 222 print("333") yield 444 gener = func() ret = gener.__next__() print(ret) ret2 = gener.__next__() print(ret2) ret3 = gener.__next__() # 最后⼀个yield执⾏完毕. 再次__next__()程序报错 print(ret3) 结果: 111 222 333 444
当程序运行完最后一个yield,那么后面继续运行__next__()程序会报错
好了生成器我们认识了,生成器有什么作用呢?
生成器有什么好处呢?就是不会一下子在内存中生成太多数据
假如我想让工厂给学生做校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,然后再去生产,我可以一件一件的要,也可以根据学生一批一批的找工厂拿。
而不能是一说要生产2000000件衣服,工厂就先去做生产2000000件衣服,等回来做好了,学生都毕业了。。。
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
send:
def generator(): print(123) content = yield 1 print('=======',content) print(456) yield2 g = generator() ret = g.__next__() print('***',ret) ret = g.send('hello') #send的效果和next一样 print('***',ret) #send 获取下一个值的效果和next基本一致 #只是在获取下一个值的时候,给上一yield的位置传递一个数据 #使用send的注意事项 # 第一次使用生成器的时候 是用next获取下一个值 # 最后一个yield不能接受外部的值
yield from
yield from 在python3中提供一种可以直接把可迭代对象中的每一个数据作为生成器的结果进行返回
def func(): lst = ['卫龙','老冰棍','北冰洋','牛羊配'] yield from lst g = func() for i in g: print(i) 结果: 卫龙 老冰棍 北冰洋 牛羊配
有个小坑,yield from 是将列表中的每一个元素返回,所以 如果写两个yield from 并不会产生交替的效果
def func(): lst1 = ['卫龙','老冰棍','北冰洋','牛羊配'] lst2 = ['馒头','花卷','豆包','大饼'] yield from lst1 yield from lst2 g = func() for i in g: print(i) 结果: 卫龙 老冰棍 北冰洋 牛羊配 馒头 花卷 豆包 大饼
推导式:
列表推导式:
列表推导式,生成器表达式以及其他推导式,首先我们先看一下这样的代码,给出一个列表,通过循环,想列表中添加1~10
li = [] for i in range(10): li.append(i) print(li)
我们换成列表推导式是什么样的,来看看:
列表推导式的常⽤写法:
[结果 for 变量 in 可迭代对象]
ls = [i for i in range(10)] print(ls)
列表推导式是通过⼀行来构建你要的列表, 列表推导式看起来代码简单. 但是出现错误之后很难排查.
lst = ['python%s' % i for i in range(1,19)] print(lst)
筛选模式:
[结果 for 变量 in 可迭代对象 if 条件]
lst = [i for i in range(100) if i %2 == 0] print(lst)
生成器推导式:
生成器表达式和列表推导式的语法基本上一样的,只是把[]换成()
gen = (i for i in range(10)) print(gen) # 结果: <generator object <genexpr> at 0x0000026046CAEBF8>
打印的结果就是一个生成器,我们可以使用for循环来循环这个生成器
gen = ("第%s次" % i for i in range(10)) for i in gen: print(i)
生成器表达式也可以进行筛选
# 获取1-100内能被3整除的数 gen = (i for i in range(1,100) if i % 3 == 0) for num in gen: print(num) # 100以内能被3整除的数的平⽅ gen = (i * i for i in range(100) if i % 3 == 0) for num in gen: print(num) # 寻找名字中带有两个e的人的名字 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] # 不用推导式和表达式 result = [] for first in names: for name in first: if name.count("e") >= 2: result.append(name) print(result) # 推导式 gen = (name for first in names for name in first if name.count('e') >= 2) for i in gen: print(i)
生成器表达式和列表推导式的区别:
1.列表推导式比较耗内存,一次性加载.生成器表达式几乎不占用内存.使用的时候才分配和使用内存
2.得到的值不一样,列表推导式得到的是一个列表.生成器表达式获取的是一个生成器
3..Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
sum(x ** 2 for x in range(4))
def func(): print(111) yield 222 g = func() # 生成器g g1 = (i for i in g) # 生成器g1. 但是g1的数据来源于g g2 = (i for i in g1) # 生成器g2. 来源g1 # list的底层有for循环,for就是一直执行__next__() 所以可以将生成器放到list中 print(list(g)) # 获取g中的数据. 这时func()才会被执行. 打印111.获取到222. g完毕. print(list(g1)) # 获取g1中的数据. g1的数据来源是g. 但是g已经取完了. g1 也就没有数据了 print(list(g2)) # 和g1同理理 print(next(g)) print(next(g1)) print(next(g2)) # 可以用next来验证 其实list就是将内容迭代了转换成了列表
字典推导式:
lst1 = ['jay','jj','meet'] lst2 = ['周杰伦','林俊杰','郭宝元'] dic = {lst1[i]:lst2[i] for i in range(len(lst1))} print(dic)
集合推导式:
集合推导式可以帮我们直接生成一个集合,集合的特点;无序,不重复 所以集合推导式自带去重功能
lst = [1,2,3,-1,-3,-7,9] s = {abs(i) for i in lst} print(s)
总结:
推导式有, 列表推导式, 字典推导式, 集合推导式, 没有元组推导式
生成器表达式: (结果 for 变量量 in 可迭代对象 if 条件筛选)
生成器表达式可以直接获取到⽣成器对象. ⽣成器对象可以直接进行for循环. ⽣成器具有惰性机制.
集合推导式和字典推导式很是类似,记住一个小技巧能够快速区分那个是字典那个是集合
字典推导式前面的结果是有个冒号,而集合的前面结果就是单纯的结果
作业:
2. 用列表推导式做下列小题 Li = [1,23,4,5,6,’a’,’b’,’c’,’d’] a. 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母 b. 求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表 c. M = [[1,2,3],[4,5,6],[7,8,9]] 求M中3,6,9组成的列表 d. 求出50以内能被3整除的数的平方,并放入到一个列表中。 e. 构建一个列表:['python1期', 'python2期', 'python3期', 'python4期', 'python6期', 'python7期', 'python8期', 'python9期', 'python10期'] f. 构建一个列表:[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] g. 构建一个列表:[0, 2, 4, 6, 8, 10, 12, 14, 16, 18] h. 有一个列表l1 = ['alex', 'WuSir', '老男孩', '太白']将其构造成这种列表['alex0', 'WuSir1', '老男孩2', '太白3'] (9)有以下数据类型: x = { 'name':'alex', 'Values':[{'timestamp':1517991992.94, 'values':100,}, {'timestamp': 1517992000.94, 'values': 200,}, {'timestamp': 1517992014.94, 'values': 300,}, {'timestamp': 1517992744.94, 'values': 350}, {'timestamp': 1517992800.94, 'values': 280} ],} 将上面的数据通过列表推导式转换成下面的类型:[[1517991992.94, 100], [1517992000.94, 200], [1517992014.94, 300], [1517992744.94, 350], [1517992800.94, 280]]