Python 协程



Python **协程**是编程中的一个基本概念,它扩展了传统函数的功能。它们在异步编程和复杂数据处理流水线中特别有用。

**协程**是函数和生成器概念的扩展。它们旨在执行协作式多任务处理和管理异步操作。

传统函数(即子程序)只有一个入口点和一个出口点,而协程可以在多个点暂停和恢复执行,这使得它们非常灵活。

协程的关键特性

以下是 Python 中协程的关键特性:

  • **多个入口点:**协程不像传统函数那样只受限于单个入口点。它们可以在遇到 `yield` 语句时暂停执行,并在稍后恢复。这允许协程处理涉及等待或处理异步数据的复杂工作流程。
  • **没有中央协调器:**传统函数(即子程序)通常由主函数协调,但协程更独立地运行。它们可以以流水线的方式相互交互,数据流经一系列协程,每个协程执行不同的任务。
  • **协作式多任务处理:**协程支持协作式多任务处理。这意味着它不是依赖于操作系统或运行时在任务之间切换,而是由程序员控制协程何时让出和恢复,从而允许对执行流程进行更细粒度的控制。

子程序与协程

**子程序**是具有单个入口点且没有暂停或恢复执行的固有机制的传统函数。它们按定义的顺序调用,并处理具有直接控制流的任务。

**协程**是具有多个入口点的更高级函数,可以暂停和恢复执行。它们对于需要异步执行、复杂控制流和数据流水线的任务很有用。它们通过允许程序员控制任务之间何时切换执行来支持协作式多任务处理。

下表有助于理解子程序和协程之间的关键区别和相似之处,使我们更容易掌握它们在编程中的各自作用和功能。

标准 子程序 协程
定义 执行任务的一系列指令。 可以暂停和恢复执行的子程序的泛化。
入口点 单个入口点。 多个入口点;可以暂停和恢复执行。
执行控制 由主函数或控制结构调用。 可以暂停执行并在以后恢复,程序员控制切换。
用途 执行特定任务或计算。 管理异步操作、协作式多任务处理和复杂工作流程。
调用机制 通常由主函数或其他子程序调用。 使用`next()`、`send()`和`close()`方法调用和控制。
数据处理 没有内置的数据交换机制;通常使用参数和返回值。 可以使用带有`send()`的`yield`接收和处理数据。
状态管理 没有固有的机制来维护调用之间的状态。 在挂起之间维护执行状态,并可以从中断处恢复。
用法 这些用于将代码模块化成可管理的块。 这些用于异步编程、管理数据管道和协作式多任务处理。
并发 并非天生设计用于并发执行;通常用于顺序编程。 支持协作式多任务处理,并可以与异步任务一起工作。
示例用法 辅助函数,实用函数。 数据管道,异步任务,协作式多任务处理。
控制流 执行遵循代码中的线性路径。 执行可以根据yield点在协程之间来回跳转。

协程的执行

协程__next__()方法启动,该方法启动协程并将执行推进到第一个yield语句。然后,协程等待向其发送值。send()方法用于向协程发送值,协程可以处理这些值并可能产生结果。

基本协程示例

协程使用yield语句,该语句可以发送和接收值。与生成器不同,生成器产生用于迭代的值,而协程通常使用yield接收输入并根据该输入执行操作。以下是Python协程的基本示例:

def print_name(prefix):
    print(f"Searching prefix: {prefix}")
    while True:
        name = (yield)
        if prefix in name:
            print(name)

# Instantiate the coroutine
corou = print_name("Welcome to")

# Start the coroutine
corou.__next__()

# Send values to the coroutine
corou.send("Tutorialspoint")
corou.send("Welcome to Tutorialspoint")

输出

Searching prefix: Welcome to
Welcome to Tutorialspoint

关闭协程

协程可以无限期运行,因此在不再需要它们时正确关闭它们非常重要。close()方法终止协程并处理清理工作。如果我们尝试向已关闭的协程发送数据,它将引发StopIteration异常

示例

以下是 Python 中关闭协程的示例:

def print_name(prefix):
    print(f"Searching prefix: {prefix}")
    try:
        while True:
            name = (yield)
            if prefix in name:
                print(name)
    except GeneratorExit:
        print("Closing coroutine!!")

# Instantiate and start the coroutine
corou = print_name("Come")
corou.__next__()

# Send values to the coroutine
corou.send("Come back Thank You")
corou.send("Thank you")

# Close the coroutine
corou.close()

输出

Searching prefix: Come
Come back Thank You
Closing coroutine!!

将协程链接起来形成管道

协程可以链接在一起形成处理管道,允许数据流经一系列阶段。这对于分阶段处理数据序列特别有用,其中每个阶段执行特定任务。

示例

以下示例显示了将协程链接起来形成管道的过程:

def producer(sentence, next_coroutine):
   '''
   Splits the input sentence into tokens and sends them to the next coroutine.
   '''
   tokens = sentence.split(" ")
   for token in tokens:
      next_coroutine.send(token)
   next_coroutine.close()

def pattern_filter(pattern="ing", next_coroutine=None):
   '''
   Filters tokens based on the specified pattern and sends matching tokens to the next coroutine.
   '''
   print(f"Searching for {pattern}")
   try:
      while True:
         token = (yield)
         if pattern in token:
            next_coroutine.send(token)
   except GeneratorExit:
      print("Done with filtering!!")
      next_coroutine.close()

def print_token():
   '''
   Receives tokens and prints them.
   '''
   print("I'm the sink, I'll print tokens")
   try:
      while True:
         token = (yield)
         print(token)
   except GeneratorExit:
      print("Done with printing!")

# Setting up the pipeline
pt = print_token()
pt.__next__()

pf = pattern_filter(next_coroutine=pt)
pf.__next__()

sentence = "Tutorialspoint is welcoming you to learn and succeed in Career!!!"
producer(sentence, pf)

输出

I'm the sink, I'll print tokens
Searching for ing
welcoming
Done with filtering!!
Done with printing!
广告