欢迎访问宙启技术站
智能推送

Python函数中如何使用yield关键字函数实现生成器

发布时间:2023-06-10 08:48:34

在 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 表达式进行处理,从而提供了一种方便的方法来捕获异常并处理