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》