2.3-生成器,推导式
生成器,推导式
生成器
初始生成器
生成器的本质就是迭代器;不同点:迭代器是python提供的已经写好的工具或通过数据转化得来的,(比如文件句柄,iter([1,2,3]),生成器是需要自己用python代码构建的工具;
生成器构建方式
三种方式
- 通过生成器函数;
- 通过生成器推导式;
- python内置函数或者模块提供;
生成器函数
通过生成器函数构建生成器
def func():
print(11)
return 22
ret = func()
print(ret)
# 将函数中的return换成yield,这样func就不是函数了,而是一个生成器
def func():
print(11)
yield 22
ret = func()
print(ret)
# <generator object func at 0x000001CEAB4CFDB0>
# 由于函数中存在yield,这个函数就是一个生成器函数;
# 在执行这个函数的时候,就不再是执行函数了,而是获取这个生成器对象;
# 生成器的本质就是迭代器,迭代器如何取值,生成器就如何取值;可以执行next()来执行生成器;
def func():
print(11)
yield 22
gener = func() # 这时候函数不会执行,而是获取到生成器
ret = gener.__next__() # 这时候函数才会执行
print(ret) # yield会将func生产出来的数据22给ret
# 生成器函数中可以写多个yield
def func():
print(11)
yield 22
print(33)
yield 44
gener = func()
ret = gener.__next__()
print(ret) # 11 22
ret2 = gener.__next__()
print(ret2) # 33 44
ret3 = gener.__next__()
print(ret3) # 最后一个yield执行完毕,再次next会报错 。 StopIteration
当程序运行完最后一个yield,后面继续运行next()程序会报错,一个yield对应一个next,next超过yield数量,就会报错,与迭代器一样;
yield与return的区别:
- return一般在函数中只设置一个,作用是终止函数,并且给函数的执行者返回值;
- yield在生成器函数中可设置多个,并不会终止函数,next会获取对应yield生成的元素;
举例:我向楼下卖包子的老板订购了10000个包子.包子铺老板非常实在,一下就全部都做出来了
def eat():
lst = []
for i in range(1, 10001):
lst.append('包子' + str(i))
return lst
e = eat()
print(e)
但是没吃完那么多,只吃了2000个,剩下8000个,占用着空间;如果老板效率高,吃一个做一个,就不会占用空间,完美
def eat():
for i in range(1, 10000):
yield '包子' + str(i)
e = eat()
for i in range(2000):
print(next(e))
两者的区别:
- 第一种是直接全部做出来,占用内存;
- 第二种是吃一个产一个,节省内存,还可以保留上次的位置;
def eat():
for i in range(1, 10000):
yield '包子' + str(i)
e = eat()
for i in range(2000):
print(next(e))
for i in range(200):
print(next(e))
# 多次next包子号码是按照顺序记录的
yield from
python3提供一种直接可以把可迭代对象中的每一个数据作为生成器的结果进行返回:
# 对比yield 和yield from
def func():
lst = ['喜洋洋', '美羊羊', '暖洋洋']
yield lst
g = func()
print(g)
# <generator object func at 0x000001682EADFDB0>
print(next(g)) # 只返回一个列表
# ['喜洋洋', '美羊羊', '暖洋洋']
def func1():
lst = ['灰太狼', '红太狼', '小灰灰']
yield from lst
g = func1()
print(g)
# 将这个可迭代对象(列表)的每个元素当成迭代器的每个结果进行返回
print(next(g))
# 灰太狼
print(next(g))
# 红太狼
print(next(g))
# 小灰灰
print(next(g))
# StopIteration
'''
yield from ['灰太狼', '红太狼', '小灰灰']
等同于:
yield '灰太狼'
yield '红太狼'
yield '小灰灰'
'''
# yield from是将列表中的每一个元素返回,如果写两个yield from并不会产生交替的效果
推导式
推导式就是构建比较有规律的列表,生成器,字典等一种简便的方式;
列表推导式
# 通过循环,向列表添加1~10
li = []
for i in range(10):
li.append(i)
print(li)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 列表推导式书写
print([i for i in range(10)])
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
列表推导式分为两种模式:
- 循环模式:[变量(加工的变量) for 变量 in iterable]
- 筛选模式:[变量(加工的变量) for 变量 in iterable if 条件]
还有多层循环
循环模式
# 将10以内所有整数的平方写入列表
lst = [i*i for i in range(1, 11)]
print(lst)
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 100以内所有的偶数写入列表
lst = [i for i in range(0, 101, 2)]
print(lst)
# 从黑色利穆1 到黑色利穆100 写入列表
lst = [f'黑色利穆{i}' for i in range(1,101)]
print(lst)
# 格式化输出的变量f'黑色利穆{i}',就是加工变量
筛选模式
筛选模式就是循环模式的基础上,加一个判断条件,将满足条件的变量留到列表中;
# 将列表中大于3的元素留下来
l1 = [1, 2, 3, 4, 5, 6, 7, 8,]
print([i for i in l1 if i > 3])
# [4, 5, 6, 7, 8]
# 三十以内可以被三整除的数
print([i for i in range(1, 31) if i % 3 == 0])
# [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
# 遭到嵌套列表中含有两个'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])
# 注意遍历顺序
生成器表达式
生成器表达式和列表推导式在语法上一样,把[] 换成()
# 把10以内所有数的平方放到一个生成器表达式中
i = (i*i for i in range(1, 11))
print(i)
# <generator object <genexpr> at 0x000002BF8DBDFDB0>
print(next(i))
# 生成器表达式进行筛选
# 获取1-100内能被3整除的数
i = (i for i in range(1,101) if i % 3 == 0)
for i in i:
print(i)
生成器表达式与列表推导式的区别:
- 列表推导式比较耗内存,所有数据一次性加载到内存。而生成器表达式遵循迭代器协议,逐个产生元素;
- 得到的值不一样,列表推导式得到的是一个列表,生成器表达式获取的是一个生成器;
- 列表推导式一目了然,生成器表达式只是一个内存地址;
推导式只能构建相对复杂的并且有规律的对象,对于没有规律,而且嵌套层数比较多(for循环超过三层)不建议使用推导式构建;
生成器的惰性机制:生成器只有在访问的时候才取值;
其他相关的推导式
字典推导式
lst1 = ['zhangsan', 'lisi', 'wangwu']
lst2 = [99, 98, 58]
dic = {lst1[i]:lst2[i] for i in range(len(lst1))}
print(dic)
# {'zhangsan': 99, 'lisi': 98, 'wangwu': 58}
集合推导式
集合推导式可以直接生成一个集合,集合的特点:无序,不重复,所以集合推导式自带去重功能;
lst = [1, 2, 3, 4, -1, -3, -8]
s = {abs(i) for i in lst} # abs() 绝对值
print(s)
# {1, 2, 3, 4, 8}