Python函数中如何使用yield关键字函数实现生成器
在 Python 中,生成器是一种能够动态生成值的特殊类型的函数。相较于计算一大批值并返回一个列表,生成器一边运行一边生成各个值,可以节省内存空间,提高程序的效率。yield 关键字在定义生成器函数时使用,它负责对生成器内部的状态进行保存,以实现暂停和继续的程序执行方式。
下面,我们就来详细讲解 Python 函数中如何使用 yield 关键字函数实现生成器。
首先,一个生成器的定义需要使用“yield”语句来返回一个值。在调用生成器时它并不会立即执行,而是返回一个迭代器对象。当我们逐步处理这个迭代器对象时,每当遇到一个 yield 语句时,生成器会向迭代器返回一个值,同时生成器暂停执行,等待下一次迭代。
例如,下面是一个简单的生成器,它会依次返回从 1 到 10 的数字:
def generator():
for i in range(1, 11):
yield i
注:range() 函数生成整数序列,左闭右开区间。比如 range(1, 11) 会返回一个整数序列,包括 1 到 10 这些数字。
下面我们将 generator() 函数转化为生成器对象:
gen = generator()
当 iterator 调用它的 __next__() 方法时,第一次生成器返回 1 时就会暂停,等待 iterator 的下一次调用,再返回 2。
print(next(gen)) print(next(gen))
输出:
1 2
我们也可以使用 for 循环语句来遍历生成器的所有值:
for i in generator():
print(i)
输出:
1 2 3 4 5 6 7 8 9 10
上述程序演示了一个最简单的生成器。 我们还可以通过更加复杂的逻辑生成器函数,更加灵活多样。
下面,我们将介绍一些有关于生成器函数的高级操作,包括迭代老版本 Python,可以在生成器之间进行协作,处理迭代器异常等等。
1.利用生成器进行迭代
通常,我们会使用 for 循环或 while 循环对 python 的内置数据类型进行迭代。 然而,有时我们需要基于某些条件或算法来动态生成一组值,这个时候生成器就是最好的选择。
例如,可以使用生成器轻松地生成费氏数列:
def fibonacci():
a, b = 0, 1
while True:
yield b
a, b = b, a + b
在 Python 2.x 中,使用生成器进行迭代需要调用 incr():
for i in fibonacci():
if i > 1000:
break
print i,
在 Python 3.x 中可以使用 next() 函数实现:
for i in fibonacci():
if i > 1000:
break
print(i, end=" ")
在这个例子中,我们对感兴趣的值进行无限迭代直到满足限制条件。
2.生成器的协作
有时候,我们需要生成器对象之间进行协作,以便进行更加高效的数据处理。 其实,这个对生成器的优势发挥得淋漓尽致,因为生成器能够暂停执行,只有在需要生成新值时才会继续执行。
例如,可以使用两个生成器对数据进行过滤等高效处理:
def fibonacci():
a, b = 0, 1
while True:
yield b
a, b = b, a + b
def filter_1():
for n in fibonacci():
if n % 2 == 0:
yield n
def filter_2():
for n in filter_1():
if n % 3 == 0:
yield n
for n in filter_2():
if n > 1000:
break
print(n)
3.内部状态管理
生成器函数内置了一个特殊的方法,我们可以使用它来维护函数内部的状态以支持生成复杂的值。
例如,“generate_random_sequence()”函数如下:
import random
def generate_random_sequence(length):
choices = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for i in range(length):
yield random.choice(choices)
如果需要生成长达 10^6 的随机序列,代码可能看起来像这样:
random_sequence = "".join(generate_random_sequence(10**6))
这个代码不经过读者特别解释会让人产生迷惑,因为Python没有生成十万级别的字符串的空间,这会导致内存几乎耗尽。这种情况下,可以使用函数“generate_random_sequence()”与“.join()”方法配合生成连续的值,而无需在运行时存储整个字符串。
但有时需要使用生成器来管理在序列生成期间累积的内部状态。 “window_slider()”函数演示了如何使用这种模式,以生成指定长度的奇怪序列。 该函数使用一个值列表来跟踪每个生成的值。
def window_slider(input_sequence, size):
input_deque = collections.deque([], maxlen=size)
for x in input_sequence:
input_deque.append(x)
if len(input_deque) == size:
yield input_deque
如果需要一个 200 个值的窗口滑动器,只需将它与其他生成器结合使用即可:
x = list(range(1, 1000000))
result = []
for window in window_slider(x, size=200):
result.append(str(sum(window)))
result = "
".join(result)
当用一种具有状态的模式生成序列时,通常先编写一个更易于理解的程序样式,然后再重构代码以使用生成器协作生成序列。这种模式提供了一种灵活的方法来调整生成算法以生成所需的结果。
在完善上述代码时,需要加入协同生成器的概念,即通过“yield from”语法向一个函数委托控制权。可以参照以下代码实现:
def null_stream():
while True:
yield
def window_slider(input_sequence, size):
input_deque = collections.deque([], maxlen=size)
send_window = null_stream()
# Send a sequence of values to the sub-generator and initialize its control flow
next(send_window)
for x in input_sequence:
input_deque.append(x)
if len(input_deque) == size:
send_window.send(input_deque)
else:
# 'next' uses 'send(None)'
next(send_window)
这个代码块的意思是当 input_deque 满了之后,就把它发送给 send_window。如果deque没有满,代码就使用 next() 挂起 send_window。这样,我们就可以在自己的生成器中使用“yield from”第三方来委托控制,而这种方法会在长序列中显著提高性能。这意味着在使用生成器时,除了特别特定的情况外,多使用“yield from”语法,这会使代码更加易于阅读,更容易维护。其中,一些更高级的操作包括异常处理,这将在下一节中详细介绍。
4. 利用生成器处理异常
最后,让我们来看一下如何在使用生成器时处理异常。当生成器中引发异常时,它可能会在任何时候使用 yield 表达式进行处理,从而提供了一种方便的方法来捕获异常并处理
