侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 747 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

Python基础(13)之生成器、各种推导式及生成器表达式

zze
zze
2019-03-11 / 0 评论 / 0 点赞 / 607 阅读 / 7587 字

不定期更新相关视频,抖音点击左上角加号后扫一扫右方侧边栏二维码关注我~正在更新《Shell其实很简单》系列

在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。

在 Python 中有三种方式来获取生成器:

  1. 通过生成器函数;
  2. 通过各种推导式来实现生成器;
  3. 通过数据的转换也可以获取生成器;

生成器函数

首先, 我们先看一个很简单的函数:

def func():
    print(1)
    return 2


ret = func()
print(ret)

'''
1
2
'''

将函数中的 return 换成 yield 就将这个函数变成了生成器:

def func():
    print(1)
    yield 2


ret = func()
print(ret)

'''
<generator object func at 0x10ca78750>
'''

运行的结果和上面不一样,为什么呢?
由于函数中存在了 yield,那么这个函数就是一个生成器函数。这个时候,我们再执行这个函数的时候,就不再是函数的执行了,而是获取这个生成器。
生成器如何使用呢?
想想迭代器,生成器的本质是迭代器,所以,我们可以直接执行 __next__() 来执行生成器:

def func():
    print(1)
    yield 2


gen = func()  # 这个时候函数不会执行,而是返回一个生成器
ret = gen.__next__()  # 这个时候函数才会执行,yield 的作用和 return 一样,也是返回数据
print(ret)

'''
1
2
'''

那么我们可以看到, yieldreturn 的效果是一样的,它们有什么区别呢?
yield 是分段来执行一个函数并返回一个返回值,return呢?直接停⽌执行函数并返回一个返回值。

def func():
    print(1)
    yield 2
    print(3)
    yield 4


gen = func()  # 这个时候函数不会执行,而是返回一个生成器
ret = gen.__next__()  # 这个时候函数才会执行,yield 的作用和 return 一样,也是返回数据
print(ret)
ret = gen.__next__()
print(ret)
ret = gen.__next__() # 最后一个 yield 执行完毕,再次 __next__() 程序报错
print(ret)

'''
1
2
3
4
Traceback (most recent call last):
  File ..., line 13, in <module>
    ret = gen.__next__() # 最后一个 yield 执行完毕,再次 __next__() 程序报错
StopIteration
'''

当程序运行完最后一个 yield,那么后面继续进行 __next__() 程序会报错。
生成器有什么作用呢?
我们来看这样一个需求,如果我们需要生成 100 万个数字,按我们之前的做法,代码如下:

def get_numbers():
    lst = []
    for i in range(1000000):
        lst.append(i)
    return lst


numbers = get_numbers()
for num in numbers:
    print(num)

下面再看生成器的做法:

def get_numbers():
    for i in range(1000000):
        yield i


number_gen = get_numbers()
print(number_gen.__next__())
print(number_gen.__next__())
print(number_gen.__next__())
print(number_gen.__next__())

'''
0
1
2
3
'''

它们的区别:

  • 第一种是直接一次性全部拿出来,会很占用内存;
  • 第二种使用生成器,一次就一个,用多少生成多少,生成器是一个一个的指向下一个,不会回去,__next__() 到哪儿, 指针就指到哪儿. 下一次继续获取指针指向的值;

接下来我们来看 send() 方法,send()__next__() 一样都可以让生成器执行到下一个 yield

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("胡辣汤")
print(ret2)
ret3 = gen.send("狗粮")
print(ret3)
ret4 = gen.send("猫粮")
print(ret4)

'''
我吃什么啊
馒头
a= 胡辣汤
大大饼
b= 狗粮
韭菜盒子
c= 猫粮
GAME OVER
'''

send()__next__() 的区别:

  1. send()next() 都是让生成器向下走一次;
  2. send() 可以给上一个 yield 的位置传递值,不能给最后一个 yield 发送值,在第一次执行生成器代码的时候不能使用 send()

生成器可以使用 for 循环来循环获取内部的元素:

def func():
    print(111)
    yield 222
    print(333)
    yield 444
    print(555)
    yield 666


gen = func()
for i in gen:
    print(i)

'''
111
222
333
444
555
666
'''

推导式

列表推导式

首先我们先看一下这样的代码,给出一个列表,通过循环,向列表中添加 1 ~ 10 之间的数:

lst = []
for i in range(1,11):
    lst.append(i)

print(lst)

'''
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
'''

替换成列表推导式:

lst = [i for i in range(1, 11)]
print(lst)

'''
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
'''

列表推导式是通过一行来构建你要的列表,列表推导式看起来代码简单,但是出现错误之后很难排查,其格式如下:

[ 结果 for 变量 in 可迭代对象 ]

例 1:生成 '小鸡 1''小鸡 10' 的字符串放到列表中。

lst = ['小鸡 %d' % i for i in range(1, 11)]
print(lst)

'''
['小鸡 1', '小鸡 2', '小鸡 3', '小鸡 4', '小鸡 5', '小鸡 6', '小鸡 7', '小鸡 8', '小鸡 9', '小鸡 10']
'''

我们还可以在生成列表时对其中的数据进行筛选,格式如下:

[ 结果 for 变量 in 可迭代对象 if 条件 ]

例 2:生成 1 ~ 10 之间的偶数列表。

lst = [i for i in range(1, 11) if i % 2 == 0]
print(lst)

'''
[2, 4, 6, 8, 10]
'''

字典推导式

根据名字应该也能猜到,字典推导式推导出来的是字典。
例 1:把字典中的 key 和 value 互换。

dic = {'a': 1, 'b': '2'}
new_dic = {dic[key]: key for key in dic}
print(new_dic)

'''
{1: 'a', '2': 'b'}
'''

例 2:在以下 list 中,从 lst1 中获取的数据和 lst2 中相对应的位置的元素组成一个新字典。

lst1 = ['jay', 'jj', 'tom']
lst2 = ['周杰伦', '林俊杰', '张三']
dic = {lst1[i]: lst2[i] for i in range(len(lst1))}
print(dic)

'''
{'jay': '周杰伦', 'jj': '林俊杰', 'tom': '张三'}
'''

集合推导式

集合推导式可以帮我们直接生成一个集合,由于集合本身就有无序、不重复的特点,所以集合推导式⾃带去重功能。
例 1:获取列表中的数字的绝对值并存放到集合中。

lst = [1, -1, 8, -8, 12]
s = {abs(i) for i in lst}
print(s)

'''
{8, 1, 12}
'''

生成器表达式

生成器表达式和列表推导式的语法基本上是一样的,只是把 [] 替换成 ()
例 1:编写可生成 '小鸡 1''小鸡 10' 的字符串的生成器表达式。

gen = ('小鸡 %d' % i for i in range(1, 11))
print(gen)
for item in gen:
    print(item)

'''
<generator object <genexpr> at 0x104627750>
小鸡 1
小鸡 2
小鸡 3
小鸡 4
小鸡 5
小鸡 6
小鸡 7
小鸡 8
小鸡 9
小鸡 10
'''

生成器表达式也可以进行筛选。
例 2:生成 1 ~ 100 中能被 3 整除的数。

gen = (i for i in range(1, 11) if i % 3 ==0)
for item in gen:
    print(item)

'''
3
6
9
'''

推导式以及生成器表达式中还可以做嵌套的循环。
例 3:寻找列表中含有 2 个 '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)
print(list(gen))

'''
['Jefferson', 'Wesley', 'Steven', 'Jennifer']
['Jefferson', 'Wesley', 'Steven', 'Jennifer']
'''

例 4:此例着重凸显懒惰的生成器表达式。

def add(a, b):
    return a + b


def test():
    for i in range(4):
        yield i


g = test()  # 0 1 2 3
for n in [2, 10]:
    g = (add(n, i) for i in g)

# 由于 n 的作用域时全局,所以到执行 list(g) 时 n 的值为 10
# g = test() # 0 1 2 3
# g = (add(10, i) for i in g) # 第一遍循环
# g = (add(10, i) for i in g) # 第二遍循环
print(list(g))
# 循环执行完毕后 g 的值如下
# g = (add(10, i) for i in (add(10, i) for i in (0,1,2,3))) # 等价于此行
# (add(10,i) for i in (10,11,12,13))
# (20,21,22,23)

'''
[20, 21, 22, 23]
'''

生成器表达式和列表推导式的区别:

  1. 列表推导式比较耗内存,一次性加载,生成器表达式几乎不占用内存,使用的时候才分配和使用内存;
  2. 得到的值不一样,列表推导式得到的是一个列表,生成器表达式获取的是一个生成器;

生成器的惰性机制

生成器只有在访问的时候才取值,说白了,你找他要它才给你值,不找它要,它是不会执行的。

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,但是 g2 的数据来源于 g1
print(list(g))  # 获取 g 中的数据,这时 func() 才会被执行,打印 111,获取到 222,g 完毕
print(list(g1))  # 获取 g1 中的数据,g1 的数据来源是 g,但是 g 已经取完了,g1 也就没有数据了
print(list(g2))  # 和 g1 同理

'''
111
[222]
[]
[]
'''
0

评论区