生成器实际上就是一个函数,称为生成器函数,与普通函数的区别是,普通函数在函数执行完毕后会通过return关键字返回函数执行的结果,但生成器函数在执行完毕后,会通过yield关键字进行返回,并且返回以后,这个函数并不会从内存中清理掉,而是会阻塞在yield语句处,等待下一次调用。
def gen(n):
m = n + 1
print("lalala")
yield m
print("hehehe")
g = gen(3)
print(g)
print(g.__next__())
print(g.__next__())
执行结果:
Traceback (most recent call last):
File "E:\Python\PythonBasis\生成器\generator01.py", line 10, in <module>
print(g.__next__())
StopIteration
<generator object gen at 0x000001EEEC2D6E30>
lalala
4
hehehe
从以上代码及函数执行结果可以看到:
- 生成器函数在调用执行后会返回一个生成器对象
- 生成器函数在被调用时,函数体中的代码只会执行到yield语句之前,yield之后的语句不会执行
- 生成器对象通过
g.__next__()
方法去获取生成器的生成值,当所有的yield
语句执行完毕后(也即函数自上而下执行完毕后),再一次调用g.__next__
方法就会抛出StopIteration
异常
因此,当你想要用生成器生成多个值时,有两种方式去实现:
方式一:在生成器函数中编写多个yield语句,如下:
#!coding:utf8
def gen():
print('lalala')
yield '春'
print("lalala")
yield '夏'
print("lalala")
yield '秋'
print("lalala")
yield '冬'
print("lalala")
g = gen()
print(g.__next__()) # 春
print("--------------------")
print(g.__next__()) # 夏
print("--------------------")
print(g.__next__()) # 秋
print("--------------------")
print(g.__next__()) # 冬
print("--------------------")
print(g.__next__()) # 该语句已没有对应的yield值了可以获取了
以上代码执行结果:
lalala
春
--------------------
lalala
夏
--------------------
lalala
秋
--------------------
lalala
冬
--------------------
lalala
Traceback (most recent call last):
File "E:\Python\PythonBasis\生成器\generator02.py", line 22, in <module>
print(g.__next__())
StopIteration
方式二: 通过在生成器函数中编写循环体,将yield语句放到循环体中从而可以实现可以yield多个值。
#!coding:utf8
import random
def gen(n): # n表示抽奖次数
print("抽奖开始!")
thanks = ["谢谢惠顾!" for i in range(7)]
awards = ["一等奖:劳斯莱斯", "二等奖:保时捷", "三等奖:自行车"]
awards.extend(thanks)
# print(awards)
for i in range(n):
yield random.choice(awards)
g = gen(3)
# print(g.__next__())
# print(g.__next__())
# print(g.__next__())
# print(g.__next__())
# 通常我们通过迭代的方式去获取生成器中的值
for i in range(3):
print(g.__next__())
以上代码的执行结果如下:
抽奖开始!
谢谢惠顾!
谢谢惠顾!
一等奖:劳斯莱斯
二、生成器的使用场景
1. 读取大文件
以下代码演示通过生成器 将一个超大文件拷贝到另一个文件中,而不会引发内存溢出问题
# 逐行读取文件的生成器函数
def my_read(filename):
f = open(filename) # <class '_io.TextIOWrapper'>, 是一个可迭代对象
for row in f:
yield row
if __name__ == '__main__':
file = my_read("./a.txt") # 获取文件中每一行的生成器对象
with open('./b.txt', 'w') as fw:
while True:
try:
row = file.__next__()
fw.write(row) # 将该行写入到新文件
except:
break # 如果捕获到异常就退出循环
2. 生成无限序列
对于无限序列,我们是不可能将整个序列存储在内存中的,但是如果我们只是想逐个的去消费这个序列中的每个值,那就可以采用生成器,以下以斐波拉契数列为例,假如我想逐个去消费斐波拉契数列中的每一项,可以采用以下方式:
# 1, 1, 2, 3, 5, 8, 13...
def Fibonacci(): # n>0
first = 1
yield first
second = 1
yield second
while True:
cur = first + second
yield cur
first = second
second = cur
fibo = Fibonacci()
for i in range(10): # 获取斐波那契数列的前10项
print(fibo.__next__(), end=' ')
以上代码的执行结果:
1 1 2 3 5 8 13 21 34 55
3. 优化内存
假设当前你需要一个1-10000的序列,下面对比一下分别使用列表和生成器两种方式去保存它,并对比两种方式的内存消耗。
import sys
def gen():
for i in range(10000):
yield i
g = gen() # 生成器
lst = [x for x in range(10000)] # 列表
print(sys.getsizeof(g))
print(sys.getsizeof(lst))
以上代码执行结果:
104
85176
这其实很好理解,以数列为例,生成器其实只是存储了这个数列的通项公式, 而列表却是存储了数列的所有值,占用内存自然会大许多
另外, 与列表推导式类似, 生成器还有另外一种实现方式, 叫做 生成器表达式
my_gen = (x for x in range(10000))
三、生成器进阶
1. yield from 语句
假设,我已经定义了一个生成0-3序列的生成器和一个生成3-10序列的生成器。现在需求变更,我想要一个生成0-10序列的生成器,那么我就可以新定义一个生成器函数,将原来的生成器函数代码各自拷贝一份,放置到新的生成器函数体中。但是这种做法很显然太麻烦,不够pythonic,所以在python3.3以后cpython为我们提供了yield from语句,当你想要把多个生成器组合起来作为一个生成器时,可以试用yield from语句
def gen01():
for i in range(3):
yield i
def gen02():
for i in range(3, 10):
yield i
gen_01 = gen01()
gen_02 = gen02()
def gen(g1, g2):
yield from g1
yield from g2
g = gen(gen_01, gen_02)
for i in range(10):
print(g.__next__(), end=' ')
以上代码执行结果:
0 1 2 3 4 5 6 7 8 9
2. send()方法
由于生成器在两次yield之间会阻塞程序,因此,我们其实是可以它阻塞期间向它传递一些值,从而改变生成器函数中原本的控制逻辑。这种传递值的方式就是send()方法。
以下演示通过生成器send()方法可以向生成器函数内部传值的特性,我们可以实现一个密码生成器,这个密码可以从函数外部通过send方法控制当前想要生成哪种形式的密码。
def gen_password():
'''
密码生成器
:return:
'''
flag = "digit"
while True:
# 默认生成6位数字密码
while flag == "digit":
tmp = yield ''.join(random.sample(string.digits, 6))
flag = flag if not tmp else tmp
while flag == "letter": # 生成6位大小写混合的密码
tmp = yield ''.join(random.sample(string.ascii_letters, 6))
flag = flag if not tmp else tmp
while flag == "lower":
tmp = yield ''.join(random.sample(string.ascii_lowercase, 6))
flag = flag if not tmp else tmp
while flag == "upper":
tmp = yield ''.join(random.sample(string.ascii_uppercase, 6))
flag = flag if not tmp else tmp
g = gen_password()
print(g.__next__())
g.send("letter")
print(g.__next__())
g.send("upper")
print(g.__next__())
print(g.__next__())
g.send("lower")
print(g.__next__())
print(g.__next__())
print(g.__next__())
以上代码执行结果如下:
685140
yPFMzQ
TYGECW
CXAVYL
cordbp
tbfmkh
ipvhtf
3. throw()方法主动抛出异常
throw()方法允许生成器对象向外抛出一个异常
def gen():
i = 0
while True:
yield i
i += 1
g = gen()
for i in g:
print(i, end=' ')
if i > 9:
g.throw(ValueError("The number is greater than 10!"))
i += 1
以上代码执行结果:
Traceback (most recent call last):
File "E:\PythonBasics\生成器\gen04.py", line 12, in <module>
g.throw(ValueError("The number is greater than 10!"))
File "E:\PythonBasics\生成器\gen04.py", line 4, in gen
yield i
ValueError: The number is greater than 10!
0 1 2 3 4 5 6 7 8 9 10
4. close()方法停止生成器
执行生成器的close方法以后,生成器就废掉了,再也不能生成任何元素了
def gen():
i = 0
while True:
yield i
i += 1
g = gen()
for i in g:
print(i, end=' ')
if i > 9:
g.close()
print(g.__next__())
以上代码执行结果:
0 1 2 3 4 5 6 7 8 9 10 Traceback (most recent call last):
File "E:\PythonBasics\生成器\gen05.py", line 14, in <module>
print(g.__next__())
StopIteration
以上就是本人关于python生成器的理解,如果一些地方理解有问题,万望指正!