在 Python 中,使用了 yield
的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield
时函数会暂停并保存当前所有的运行信息,返回 yield
的值, 并在下一次执行 next()
方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。
在 Python 中有三种方式来获取生成器:
- 通过生成器函数;
- 通过各种推导式来实现生成器;
- 通过数据的转换也可以获取生成器;
生成器函数
首先, 我们先看一个很简单的函数:
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
'''
那么我们可以看到, yield
和 return
的效果是一样的,它们有什么区别呢?
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__()
的区别:
send()
和next()
都是让生成器向下走一次;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]
'''
生成器表达式和列表推导式的区别:
- 列表推导式比较耗内存,一次性加载,生成器表达式几乎不占用内存,使用的时候才分配和使用内存;
- 得到的值不一样,列表推导式得到的是一个列表,生成器表达式获取的是一个生成器;
生成器的惰性机制
生成器只有在访问的时候才取值,说白了,你找他要它才给你值,不找它要,它是不会执行的。
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]
[]
[]
'''
评论区