Python学习-生成器、列表推导式、生成器表达式、字典推导式、集合推导式

记录下python中生成器、列表推导式、生成器表达式、字典推导式、集合推导式的内容。

生成器

生成器本质上就是迭代器,是自己用python代码构建出的一种数据结构,获取生成器有三种方式:

  1. 使用生成器函数
  2. 使用生成器表达式
  3. 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》

posted @ 2021-09-12 05:52  斐波那切  阅读(121)  评论(0编辑  收藏  举报