Python 中的yield关键字通常与生成器的使用有关。在本教程中,让我们也来看看Python中的yield关键字和生成器。生成器在Python中用来完成与处理大量数据有关的任务,并以一种节省内存的方式完成。for 循环中的 yield 关键字为在 Python 中使用生成器提供了一种很好的优雅语法。
一个标准的函数
首先,让我们看看一个函数,它接收一个数字列表,然后返回每个数字的立方体。为了实现这个目标,我们首先定义了一个名为cubed_numbers() 的函数。它接收了一个数字列表,然后对每个数字进行立方运算。当每个数字被立方化时,它将通过使用append()方法被添加到结果列表中。最后,结果被返回。
def cubed_numbers(n):
result = []
for i in n:
result.append(i ** 3)
return result
现在我们可以调用cubed_numbers()函数,它的执行情况正如我们所期望的。数字1、2、3、4和5变成了1、8、27、64和125。
my_cubes = cubed_numbers([1, 2, 3, 4, 5])
print(my_cubes)
[1, 8, 27, 64, 125]
转换为生成器
为了将cubed_numbers()函数转换成生成器函数,我们可以做一些改变。我们删除了results[]列表,以及返回语句。由于我们没有列表,我们就不能再使用append()方法。在for循环中,我们首次出现了yield关键字。
def cubed_numbers(n):
for i in n:
yield i ** 3
现在调用这个函数的结果是不同的。我们得到的不是一个结果的列表,而是一个生成器对象。
my_cubes = cubed_numbers([1, 2, 3, 4, 5])
print(my_cubes)
<generator object cubed_numbers at 0x000002C0736DAC80>
原因是生成器不在内存中保留整个结果,而是一次产生一个结果。所以这个生成器正在等待我们询问下一个结果。
next()的介绍
好了,这个生成器没有输出任何东西,而且只用了少量的内存。很好。现在我怎样才能看到一个结果呢?我们可以通过调用 next() 来查看生成器计算的结果。
my_cubes = cubed_numbers([1, 2, 3, 4, 5])
print(next(my_cubes))
1
嘿,我的答案都在哪里?next()函数只深入到生成器中,拉出一个单一的值。然后它把指针移到下一个可用的值上,但并不马上返回它。如果我们再次调用 next() ,我们应该看到下一个结果。
my_cubes = cubed_numbers([1, 2, 3, 4, 5])
print(next(my_cubes))
print(next(my_cubes))
1
8
如果我们想看到全部5个结果,我们需要像这样调用next()5次。
my_cubes = cubed_numbers([1, 2, 3, 4, 5])
print(next(my_cubes))
print(next(my_cubes))
print(next(my_cubes))
print(next(my_cubes))
print(next(my_cubes))
1
8
27
64
125
停止迭代错误
如果你试图调用 next() 的次数超过了生成器中的数值,你会得到一个 StopIteration 异常。这意味着整个生成器的内容已经用完了,现在已经没有值了。
my_cubes = cubed_numbers([1, 2, 3, 4, 5])
print(next(my_cubes))
print(next(my_cubes))
print(next(my_cubes))
print(next(my_cubes))
print(next(my_cubes))
print(next(my_cubes))
1
8
27
64
125
Traceback (most recent call last):
File "C:\python\justhacking\howtoyield.py", line 12, in <module>
print(next(my_cubes))
StopIteration
带有For循环的生成器
上面的代码不是你在实际使用生成器时看到的,尤其是使用生成器的想法是在不消耗大量内存的情况下处理大量数据。yield关键字经常在for循环中使用。让我们在到目前为止的所有代码中看看这个。
def cubed_numbers(n):
for i in n:
yield i ** 3
my_cubes = cubed_numbers([1, 2, 3, 4, 5])
for cube in my_cubes:
print(cube)
1
8
27
64
125
通过简单地在生成器上循环,并在循环内部使用 yield 关键字,Python 足够聪明,可以得到所有的值,并在耗尽之前停止,从而防止出现 StopIteration 错误。
生成器的理解
我们已经在不同的教程中看到了Python 列表理解的工作原理,生成器也有类似的功能。不同的是,你可以使用周围的**[ ]字符,而不是使用周围的( )**字符,如下图所示。
my_cubes = (i ** 3 for i in [1, 2, 3, 4, 5])
for cube in my_cubes:
print(cube)
1
8
27
64
125
生成器的性能
我们可以通过设置两个不同的函数对500万个整数进行立方运算,来证明Python产生器与返回器的性能。这是一个相当大的数字,通过使用Pythons time.perf_counter()和memory_profiler.memory_usage()函数,我们可以确定立方体500万个整数需要多少内存以及使用每种方法立方体500万个整数需要多少时间。第一个函数叫做cubed_list(),它使用一个标准的for循环,结合一个空列表,每次计算每个整数的立方,然后将其追加到列表中。一旦所有的整数都被立方化,结果就会被返回。第二个函数被命名为 cubed_generator(),我们只是使用 Python 中的 yield 关键字来代替每次计算的追加。
列表性能
import memory_profiler as mem_profile
import random
import time
mem_before = mem_profile.memory_usage()[0]
print(f'Before calling the function, Python is using {mem_before} MB of memory')
def cubed_list(n):
result = []
for i in range(n):
result.append(i ** 3)
return result
def cubed_generator(n):
for i in range(n):
yield i ** 3
time_start = time.perf_counter()
cubes = cubed_list(5000000)
time_end = time.perf_counter()
elapsed = time_end + time_start
mem_after = mem_profile.memory_usage()[0]
mem_usage = mem_after - mem_before
print(f'After calling the function, Python is using {mem_after} MB of memory')
print(f'It Took {elapsed} Seconds to cube 5,000,000 integers')
Before calling the function, Python is using
我们可以看到,内存的使用量激增,完成这个任务需要4秒钟。
生成器的性能
import memory_profiler as mem_profile
import random
import time
mem_before = mem_profile.memory_usage()[0]
print(f'Before calling the function, Python is using {mem_before} MB of memory')
def cubed_list(n):
result = []
for i in range(n):
result.append(i ** 3)
return result
def cubed_generator(n):
for i in range(n):
yield i ** 3
time_start = time.perf_counter()
cubes = cubed_generator(5000000)
time_end = time.perf_counter()
elapsed = time_end + time_start
mem_after = mem_profile.memory_usage()[0]
mem_usage = mem_after - mem_before
print(f'After calling the function, Python is using {mem_after} MB of memory')
print(f'It Took {elapsed} Seconds to cube 5,000,000 integers')
Before calling the function, Python is using
这一次,内存使用量几乎没有变化,只用了2秒就完成了任务。正如我们所看到的,使用yield关键字的Generator版本表现得非常好,对内存的影响很小。
了解更多关于 Python Yield 关键字的信息
- Yield 关键字的作用是什么(stackoverflow.com)
- Python 中的Yield关键词有什么作用 (machinelearningplus.com)
- Python教程 Yield in Python(simplilearn.com)
- 如何在Python中使用Yield关键词(kite.com)
- Python如何使用Yield (studytonight.com)
- Python生成器简介 (realpython.com)
- 带有Yield关键字的Python函数是一个生成器迭代器(bogotobogo.com)
- Python生成器技巧(book.pythontips.com)
- Python中生成器的基础知识(pythonforbeginners.com)
- Python生成器简介 (realpython.com)
- Python生成器(tutorialsteacher.com)
- Python生成器(learnpython.org)
Python 产量关键词总结
Python中的yield关键字和生成器提供了一种处理大型数据集的简洁方法。它们有一个很好的可读语法,而且往往对内存友好,同时具有很高的性能。除了 yield 关键字本身,我们还看到了使用**( )**字符创建生成器的类似速记的理解性语法。
