Python学习-生成器、列表推导式、生成器表达式、字典推导式、集合推导式
2021/9/12 9:04:44
本文主要是介绍Python学习-生成器、列表推导式、生成器表达式、字典推导式、集合推导式,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
记录下python中生成器、列表推导式、生成器表达式、字典推导式、集合推导式的内容。
生成器
生成器本质上就是迭代器,是自己用python代码构建出的一种数据结构,获取生成器有三种方式:
- 使用生成器函数
- 使用生成器表达式
- python内部提供
下面使用生成器函数,来创建生成器,关键字为yield。
# 生成器函数 def func(): print('我不是函数,我是生成器') yield 520 print('是否执行') yield 1314 ret=func() # 此时func不执行 print(ret) # <generator object func at 0x105bffcf0> 说明是生长器 print(next(ret)) print(next(ret)) # 执行结果 我不是函数,我是生成器 520 是否执行 1314
可以看出520和1314均是yield返回的结果,它与return类型也能返回数据,两者有什么区别呢。
- return:会终止函数,如果有多个return只有一个生效,并且return会返回值给函数的执行。
- yield:只要一个函数中有yield,那么它就是生成器函数,并且一个next对应一个yield,执行到函数体哪里也是可以用光标的位置去理解。
惰性机制
生成器到底有什么优势呢,看一个例子就明白,它利用了惰性机制,不要值就坚决不取值。
如果去菜市场买鸡蛋,想要200个鸡蛋,不使用生成器使用列表接收,就一次性得到200个鸡蛋。如果使用生成器,就可以分批次来取,不需要一次拿200个鸡蛋,这就是使用生成器的不同,可以节省内存空间,不需要一次性放到内存里,取多少拿多少。
# 没有使用生成器 def get_eggs(): li=[] for i in range(1,201,1): li.append(f'鸡蛋{i}号') print(li) # 一次性得到200个鸡蛋 get_eggs() # 使用生成器函数 def get_eggs_2(): for i in range(1,201,1): yield f'鸡蛋{i}号' ret=get_eggs_2() print('----第一次消费了100个鸡蛋----') for r in range(1,101,1): # 消费了1-100的鸡蛋 print(next(ret)) # 再执行一次 print('----第二次消费了100个鸡蛋----') for r in range(1,101,1): # 消费了101-200的鸡蛋 print(next(ret))
yield与yield from
yield from一个可迭代对象,其实就跟写了多个yield没区别,看下面例子。
# yield def func(): li=[1,2,3,4,5] yield li ret=func() print(next(ret)) # [1,2,3,4,5] # yield from def func(): li=[1,2,3,4,5] # 类似写了5个yield yield from li ret=func() print(next(ret)) print(next(ret)) print(next(ret)) print(next(ret)) print(next(ret)) # 执行结果 1 2 3 4 5
可以在函数中定义多个yield from,使用for循环可以直接从生成器取值。
def func(): l1=['messi','herry','ronald'] l2=['梅西','亨利','罗纳尔多'] yield from l1 yield from l2 ret=func() # 生成器可以使用for循环获取内部的元素 for i in ret: print(i,type(i)) # 执行结果 messi <class 'str'> herry <class 'str'> ronald <class 'str'> 梅西 <class 'str'> 亨利 <class 'str'> 罗纳尔多 <class 'str'>
next与send
send()和next()都是让生成器向下走一个yield位置,但send可以给上⼀个yield的位置传递值, 不能给最后一个yield发送值,在第一次执⾏生成器代码的时候不能使⽤send。
def eat(): print("我吃什什么啊") a = yield "馒头" print("a=",a) b = yield "⼤大饼" print("b=",b) c = yield "⾲韭菜盒⼦子" print("c=",c) yield "GAME OVER" gen = eat() # 获取生成器 ret1 = gen.__next__() print(ret1) ret2 = gen.send("胡辣汤") # send到a的位置 print(ret2) ret3 = gen.send("狗粮") # send到b的位置 print(ret3) ret4 = gen.send("猫粮") # send到c的位置 print(ret4) # 执行结果 我吃什什么啊 馒头 a= 胡辣汤 ⼤大饼 b= 狗粮 ⾲韭菜盒⼦子 c= 猫粮 GAME OVER
列表推导式
列表推导式为用一行代码,构建一个比较复杂并且有规律的列表,列表推导式分为两种:
- 循环模式:[加工后变量 for 变量 in iterable]
- 筛选模式:[加工后变量 for 变量 in iterable if 条件]
首先感受一下列表推导式与普通方式构建一个有规律列表的区别。下面列表推导式使用的是循环模式。
# 使用普通方式构建列表[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] li = [] for i in range(1, 11, 1): li.append(i) print(li) # 使用列表推导式 --是循环模式 li = [i for i in range(1, 11)] print(li) # 执行结果均一样 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
再看几个循环模式的例子。
# 1 将10以内的所有整数的平方写入到列表 li = [i ** 2 for i in range(1, 11)] print(li) # 2 100以内所有的偶数写入到列表 li = [i for i in range(2, 102, 2)] print(li) # 3 将python1期到python200期写入列表 li = [f'python{i}期' for i in range(1, 201)] print(li)
当在列表推导式末尾加上判断条件,就变成筛选模式。
# 1 30以内能被3整除的数 li = [i for i in range(1, 31) if i % 3 == 0] print(li) # 2 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母 li = ['messi', 'ronald', 'herry', 'a', 'b', 'c'] li_2 = [s.upper() for s in li if len(s) > 2] print(li_2) # 3 找到嵌套列表中名字含有两个'e'的所有名字,并全大写 names = [['tom', 'jerry', 'jefferson', 'andrew', 'steven'], ['alice', 'ana', 'wendy', 'jennifer', 'sherry']] # 正常写法 li = [] for name in names: for s in name: # 使用count方法判断 if s.count('e') == 2: li.append(s.upper()) print(li) # 使用列表推导式,比较牛逼的写法 li=[name.upper() for l in names for name in l if name.count('e')==2] print(li) # 执行结果 [3, 6, 9, 12, 15, 18, 21, 24, 27, 30] ['MESSI', 'RONALD', 'HERRY'] ['JEFFERSON', 'STEVEN', 'JENNIFER'] ['JEFFERSON', 'STEVEN', 'JENNIFER']
列表推导式给人的感觉就是简洁高逼格,人类高质量代码的味道,但是一旦出现问题也不好查,其主要特点如下。
- 有毒,会让人沉迷,比较适合构建有规律的的列表
- 需要三层循环才能构建的列表或其他可迭代对象,不建议使用
- 不方便debug查找问题
感受一下高逼格的写法。
# 构建一个列表[2,3,4,5,6,7,8,9,'J','Q','K','A'] # 创建列表的两种方式,列表推导式和list创建 li=[s for s in range(2,10)]+list('JQKA') print(li)
生成器表达式
生成器表达式与列表推导式几乎一样,写时需要加一个小括号,同样有循环模式和筛选模式。下面打印li的类型为generator,说明为生成器。
# 生成器表达式 li=(i for i in range(1,11)) print(li,type(li)) print(next(li)) print(next(li)) print(next(li)) print(next(li)) # 执行结果 <generator object <genexpr> at 0x107c9ed68> <class 'generator'> 1 2 3 4
列表推导式和生成器表达式有啥区别呢,主要如下:
1 写法有区别,列表推导式使用[],生成器表达式使用()
2 生成器表达式本质是iterator,列表推导式本质上是iterable
li=[i for i in range(1,11)] print('__iter__' in dir(li)) # True,说明列表推导式本质上是iterable li=(i for i in range(1,11)) print('__iter__' in dir(li) and '__next__' in dir(li)) # True,说明生成器表达式本质是iterator
字典推导式
类似列表推导式,了解即可。
# 将l1的元素作为key,l2的元素作为value组合成字典 l1=['name','age','salary'] l2=['clyang',22,9000] li={l1[i]:l2[i] for i in range(len(l1))} print(li) # 将下面字典key-value调换,变成新字典 dic={'name':'clyang','age':33} new_dic={dic[key]:key for key in dic} print(new_dic) # 执行结果 {'name': 'clyang', 'age': 22, 'salary': 9000} {'clyang': 'name', 33: 'age'}
集合推导式
类似列表推导式,只是集合没有重复元素,了解即可。
l1=[1,2,3,3,-4,-5,-6] s={abs(number) for number in l1} print(s) # 执行结果 {1, 2, 3, 4, 5, 6}
相关练习
(1)看代码写结果,这个题巨坑,第一次就搞错了。
def add(a, b): return a + b def test(): for r_i in range(4): yield r_i g = test() for n in [2, 10]: g = (add(n, i) for i in g) # 咋一看就是(12,13,14,15) print(list(g)) # 实际是[20, 21, 22, 23]
其实上面for循环控制次数2次,执行两次后g=(add(n,i) for i in (add(n,i) for i in g)),执行到最后n等于10,所以g=(add(10,i) for i in (add(10,i) for i in g))。
(2)列表推导式使用练习
# 1 求出50以内能被3整除的数的平方,并放入到列表中 li=[i**2 for i in range(1,50,1) if i%3==0] print(li) # 2 构建一个列表:[(0,1),(1,2),(2,3),(3,4),(4,5),(5,6)] li=[(i,i+1) for i in range(6)] print(li) # 3 有一个列表l1=['alex','wusir','老男孩','太白'] # 将其构造成这种列表['alex0','wusir1','老男孩2','太白3'] l1=['alex','wusir','老男孩','太白'] li=[l1[index]+str(index) for index in range(4)] print(li) # 4 现有如下数据 x={ 'name':'alex', 'value':[{'timestamp':190111,'value':100}, {'timestamp':200222,'value':200}, {'timestamp':210333,'value':300}, {'timestamp':220444,'value':400}, {'timestamp':230555,'value':500}, ] } # 将上面的数据通过列表推导式转换成下面的类型 # [[190111,100],[200222,200],[210333,300],[220444,400],[230555,500]] li=[[dic['timestamp'],dic['value']] for dic in x.get('value')] print(li) # 执行结果 [9, 36, 81, 144, 225, 324, 441, 576, 729, 900, 1089, 1296, 1521, 1764, 2025, 2304] [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] ['alex0', 'wusir1', '老男孩2', '太白3'] [[190111, 100], [200222, 200], [210333, 300], [220444, 400], [230555, 500]]
(3)使用2层循环列表推导式,构建笛卡尔积。
# 有一个列表,里面包含三种不同尺寸的T恤,每种尺寸都有两种颜色,使用列表推导式构建尺寸和颜色的笛卡尔积 colors=['black','white'] sizes=['S','M','L'] li=[(color,size) for color in colors for size in sizes] print(li) # 6 构建一个列表,打印扑克牌中除大小王,所有的牌类 shapes=['♥','♠','♣','♦'] numbers=['A','2','3','4','5','6','7','8','9','10','J','Q','K'] li=[(shape,number) for shape in shapes for number in numbers] print(li) # 执行结果 [('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')] [('♥', 'A'), ('♥', '2'), ('♥', '3'), ('♥', '4'), ('♥', '5'), ('♥', '6'), ('♥', '7'), ('♥', '8'), ('♥', '9'), ('♥', '10'), ('♥', 'J'), ('♥', 'Q'), ('♥', 'K'), ('♠', 'A'), ('♠', '2'), ('♠', '3'), ('♠', '4'), ('♠', '5'), ('♠', '6'), ('♠', '7'), ('♠', '8'), ('♠', '9'), ('♠', '10'), ('♠', 'J'), ('♠', 'Q'), ('♠', 'K'), ('♣', 'A'), ('♣', '2'), ('♣', '3'), ('♣', '4'), ('♣', '5'), ('♣', '6'), ('♣', '7'), ('♣', '8'), ('♣', '9'), ('♣', '10'), ('♣', 'J'), ('♣', 'Q'), ('♣', 'K'), ('♦', 'A'), ('♦', '2'), ('♦', '3'), ('♦', '4'), ('♦', '5'), ('♦', '6'), ('♦', '7'), ('♦', '8'), ('♦', '9'), ('♦', '10'), ('♦', 'J'), ('♦', 'Q'), ('♦', 'K')]
(4)看下面代码,能否对其进行简化。
def chain(*args): for it in args: # for i in it: # yield i # 上面使用yield from来简化,优化内存循环,提高了效率 yield from it # 调用 g=chain('messi',[1,2,3]) # next调用 print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) # 或者将生成器转换成列表,然后再循环打印 #li=list(g) #for item in li: # print(item) # 执行结果 m e s s i 1 2 3
yield from可以直接作用可迭代对象(这里为元祖),相当于多个yield,优化内存循环,提高了效率。
(5)看代码写结果
v=[i%2 for i in range(10)] print(v) # [0, 1, 0, 1, 0, 1, 0, 1, 0, 1] k=(i%2 for i in range(10)) print(k) # # 看代码求结果(面试题) def demo(): for i in range(4): yield i g=demo() # 以下都是生成器表达式 g1=(i for i in g) # 注意g1是生成器 g2=(i for i in g1) # g2来自g1 print(list(g1)) # 在list方法里已经对g1取值完了 print(g1) # print(list(g2)) # g1已经取完,g2为[] # 执行结果 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] <generator object <genexpr> at 0x10b6bfd68> [0, 1, 2, 3] <generator object <genexpr> at 0x10b6bfcf0> []
PS:以上,理解不一定正确,学习就是一个不断认识和纠错的过程,如果有误还请批评指正。
写于9.12凌晨5点:我留下她的东西,现在不管是否有留恋的意思,都改变不了对你的伤害,真心对不起。自己情绪很不稳定内心也扭曲,希望以后不要再伤害到你,不管以后我们走向何方,都希望你能永远幸福、快乐、青春。
参考博文:
(1)《老男孩python》
这篇关于Python学习-生成器、列表推导式、生成器表达式、字典推导式、集合推导式的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-03用FastAPI掌握Python异步IO:轻松实现高并发网络请求处理
- 2025-01-02封装学习:Python面向对象编程基础教程
- 2024-12-28Python编程基础教程
- 2024-12-27Python编程入门指南
- 2024-12-27Python编程基础
- 2024-12-27Python编程基础教程
- 2024-12-27Python编程基础指南
- 2024-12-24Python编程入门指南
- 2024-12-24Python编程基础入门
- 2024-12-24Python编程基础:变量与数据类型