生成器的概念
一句话解释:包含了yield关键字的函数就是生成器,它的返回值是一个生成器对象。
Python 中的生成器(Generator)是十分有用的工具,它能够方便地生成迭代器(Iterator)。
创建以及使用
def my_gen():
yield 1
yield 2
yield 3
gen = my_gen()
#生成器可以由next()调用
while True:
try:
next(gen)
except StopIteration:
print('Done..')
break
#next() 调用太啰嗦,通常我们用迭代的方式获取生成器的值:
gen = my_gen()
for item in gen:
print(item)
生成器还有一种更简单的写法,像这样:
# 列表推导式
my_list = [x for x in range(10000)]
# 生成器表达式
my_gen = (x for x in range(10000))
yield的理解
1.yield相当于return。
2.函数遇到yield就暂停,保存当前信息,返回yield的值。
3.在下次执行next()时,从当前位置继续执行。
yield关键字有两点作用:
保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
将 yield 关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
验证
def Fibonacci(all_nums):
a, b = 0, 1
current_num = 0
print("----1----")
while current_num < all_nums:
print("----2----")
yield a # 如果一个函数中有yield语句,那么这个就不再是函数了,而是一个生成器的模板
print("----3----")
a, b = b, a+b
print("----4----")
current_num += 1
print("----5----")
return "done"
# 如果在调用Fibonacci的时候,发现这个函数中有yield,那么此时不再是调用函数,而是创建了一个生成器对象
fib = Fibonacci(5)
ret = next(fib)
print("----6----")
print(ret)
ret = next(fib)
print("----7----")
print(ret)
ret = next(fib)
print("----9----")
print(ret)
生成器的应用
读取大文件
假如你读取大文件这样写,通常这都是没啥问题的。但如果这个文件非常非常大,那么将会得到内存溢出的报错。
def csv_reader(file_name):
file = open(file_name)
result = file.read().split("\n")
return result
可以用生成器这样写
由于这个版本的 csv_reader() 是个生成器,因此你可以通过遍历,加载一行、处理一行,从而避免了内存溢出的问题。
def csv_reader(file_name):
for row in open(file_name, "r"):
yield row
无限序列
由于生成器一次只生成一个值,因此它可用于表示无限数据。
def all_even():
n = 0
while True:
yield n
n += 2
even = all_even()
for i in even:
print(i)
优化内存
def gen():
for x in range(10000):
yield x
# 生成器
my_gen = gen()
# 列表
my_list = [x for x in range(10000)]
比较他们的大小
>>> import sys
>>> sys.getsizeof(my_list)
87616
>>> sys.getsizeof(my_gen)
112
生成器组合
有时候你需要把两个生成器组合成一个新的生成器,比如:
gen_1 = (i for i in range(0,3))
gen_2 = (i for i in range(6,9))
def new_gen():
for x in gen_1:
yield x
for y in gen_2:
yield y
for x in new_gen():
print(x)
# 输出:
# 0
# 1
# 2
# 6
# 7
# 8
这种组合迭代的形式不太方便,因此 Python 3.3 引入新语法 yield from 后,可以改成这样:
def new_gen():
yield from gen_1
yield from gen_2
生成器进阶语法
send()
我们除了可以使用 next() 函数来唤醒生成器继续执行外,还可以使用 send() 函数来唤醒执行。使用 send() 函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。
def gen():
count = 0
while True:
count += (yield count)
>>> g = gen()
>>> g.send(None)
0
>>> g.send(1)
1
>>> g.send(2)
3
>>> g.send(5)
8
throw()
.throw() 允许用生成器抛出异常
def my_gen():
count = 0
while True:
yield count
count += 1
gen = my_gen()
for i in gen:
print(i)
if i == 3:
gen.throw(ValueError('The number is 3...'))
# 输出:
# 0
# 1
# 2
# 3
# ValueError: The number is 3...
close()
.close() 可以停止生成器
def my_gen():
count = 0
while True:
yield count
count += 1
gen = my_gen()
for i in gen:
print(i)
if i == 3:
gen.close()
结论
以上就是生成器的大致介绍了。它可以暂停控制流,并在你需要的时候随时回到控制流,从上一次暂停的位置继续执行。
生成器有助于你处理大型数据流或者表达无限序列,是生成迭代器的有用工具。