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

Python中的生成器函数:yield的应用

发布时间:2023-06-09 10:58:16

生成器函数是 Python 中一种特殊的函数,可以在其执行过程中动态地生成一系列值。yield 是生成器函数中的关键字,它的作用是向主程序返回值,同时暂停生成器函数的执行,等待主程序再次调用该函数时继续执行。

yield 的应用十分广泛,下面我们将介绍三个常见的应用场景。

1. 用于迭代器的实现

生成器函数经常用于实现自定义迭代器,这种迭代器可以动态地生成数据集合,从而方便地遍历数据。例如,下面是实现自动增加的迭代器的示例代码:

def auto_increment(start=0, step=1):
    num = start
    while True:
        yield num
        num += step

这个函数的作用是返回一个自动增加的数列,可以指定起始值和步长。程序可以不断调用该函数,并使用 next() 函数获取数列中的下一个值。下面是如何使用该函数的示例代码:

ai = auto_increment(5, 2)
print(next(ai)) # 输出 5
print(next(ai)) # 输出 7
print(next(ai)) # 输出 9

2. 用于协程的实现

协程是一种轻量级的线程,可以在同一个进程内并发地执行多个任务。一个协程通常包含多个状态,可以通过 yield 关键字暂停和恢复状态之间的转移。例如,下面是实现协程的示例代码:

def coro():
    print("READY")
    value = yield  # 首次调用 yield 时函数暂停并等待主程序的输入
    print("GOT: " + value)
    yield "OK"     # 第二次调用 yield 时函数再次暂停并等待主程序的输入
     
c = coro()          # 创建一个协程对象
next(c)             # 调用一次 next() 函数,使协程运行到第一个 yield 语句处
c.send("DATA")      # 给协程发送一个数据,协程运行到第二个 yield 语句处

这个程序中,coro() 函数定义了一个协程,其中包含两个 yield 语句。程序创建一个协程对象 c,并使用 next() 函数调用一次该对象,使得程序执行到第一个 yield 语句处暂停,并等待主程序传入一个值。接着,程序使用 send() 函数给协程发送一个数据,使得程序执行到第二个 yield 语句处暂停,并再次等待主程序输入。最终,程序输出 "GOT: DATA",完成协程的运行。注意,第一次调用 yield 语句时,返回的值并没有被赋给任何变量,因此必须等主程序再次调用 send() 函数并给协程传入一个值之后,该值才会被赋给 value 变量。

3. 用于实现协作式多任务

生成器函数还可以用于实现协作式多任务,例如实现一个简单的协作式任务调度器。该调度器可以将多个协程对象加入到任务队列中,按照指定的优先级和运行时间进行调度,并一次性运行多个协程对象。

import heapq
 
class Task:
    def __init__(self, priority, time, coroutine):
        self.priority = priority
        self.time = time
        self.coroutine = coroutine
 
    def __lt__(self, other):
        return (self.priority, self.time) < (other.priority, other.time)
 
def run(tasks):
    heap = []
    for task in tasks:
        heapq.heappush(heap, task)
    while heap:
        task = heapq.heappop(heap)
        try:
            value = task.coroutine.send(task.time)
            if value is not None:
                heapq.heappush(heap, Task(task.priority, value, task.coroutine))
        except StopIteration:
            pass

这个程序中,Task 类表示每个任务,包含优先级、运行时间和协程对象。run() 函数用于执行多个协程对象,给协程对象添加 time 参数,可以强制在指定时间内暂停它的执行,并将它加入到任务队列的 heap 中。程序每次从任务队列中取出一个任务并运行,如果协程对象运行完毕,它会被从 heap 中删除。

例如,假设我们要对以下两个协程对象进行调度:

def foo():
    for i in range(3):
        print("FOO", i)
        yield i
 
def bar():
    for i in range(5):
        print("BAR", i)
        yield (i + 1) * 10

我们可以使用 run() 函数调用这两个协程对象:

run([Task(1, 0, foo()), Task(3, 0, bar())])

该程序会先执行 foo() 协程对象,输出 "FOO 0",然后暂停并将协程对象加入到队列 heap 中。接着程序执行 bar() 协程对象,输出 "BAR 0",然后同样暂停并加入到队列中。第二次进入主循环时,程序从队列 heap 中取出 foo() 协程对象,并给它传入时间参数 0。协程对象继续执行,输出 "FOO 1",并再次暂停并加入到队列中。接着程序再次从队列 heap 中取出 bar() 协程对象,并给它传入时间参数 0。这样来回交替执行,直到所有协程对象运行结束。

以上就是 yield 在生成器函数中的三个应用场景:迭代器、协程和任务调度。如果你还没有掌握生成器函数和 yield 的使用方法,可以参考文中的示例代码进行实践。在实际开发中,熟练掌握生成器函数和 yield 可以帮助我们优化代码结构,提高代码效率和可维护性。