likes
comments
collection
share

Python 高级编程之生成器与协程进阶(五)

作者站长头像
站长
· 阅读数 8

一、概述

  • 生成器是一种在 Python 中的迭代器生成器。生成器是一个函数,它生成一个迭代器。当生成器函数被调用时,它不会立即执行,而是返回一个生成器对象,该对象可以被用于迭代。生成器可以利用 yield 语句在函数内部生成值,并在函数调用者处接收这些值。

  • 协程是一种高效的、内存友好的、线程内的并发技术,它可以让您在单个线程内并发地执行多个任务。协程是通过使用 async 关键字实现的,并可以在 Python 中的 asyncio 库中使用。与线程不同,协程不需要额外的系统线程,因此它们比线程更高效、更灵活。

在简单的说法,生成器用于生成一系列的值,而协程用于在单个线程中并发执行多个任务。

Python 高级编程之生成器与协程进阶(五)

二、生成器

生成器表达式本质上就是一个迭代器,是定义迭代器的一种方式,是允许自定义逻辑的迭代器。生成器使用generator表示。

  • 生成器可以使用 for 循环或 next() 函数来遍历。当生成器对象被创建时,它会保存函数的当前状态,并在每次调用 next() 或 for 循环时从当前状态开始执行,直到遇到 yield 语句为止。

  • 生成器遇到 yield 语句时,它会生成当前的值,并保存函数的当前状态,以便下次调用时可以从该状态开始继续执行。当生成器再次被调用时,它会继续执行从上次暂停的地方开始,直到遇到下一个 yield 或者 return 语句,或者函数结束为止。

下面是一个生成器函数的示例:

def my_generator():
    for i in range(3):
        yield i

gen = my_generator()
for i in gen:
    print(i)

输出:

0
1
2

从上面的示例可以看出,生成器的工作原理是通过保存函数的当前状态,以便每次调用时从当前状态开始继续执行,并使用 yield 语句生成值的。

1)生成器和迭代器的区别

生成器和迭代器是 Python 中的两个相关的概念,但是有一些区别:

  • 定义生成器是一种特殊的迭代器,它可以生成一系列的值,而迭代器是一个对象,它实现了 iternext 方法,可以返回一个值的序列。

  • 创建:生成器可以通过定义生成器函数,在函数内部使用 yield 语句生成值;迭代器可以通过定义迭代器类,在类中实现 iternext 方法。

  • 效率:生成器函数在生成值时只需要暂停函数的执行,因此它具有更高的效率;迭代器类需要维护一个对象状态,因此效率较低。

  • 用途:生成器适用于生成大量的数据,因为它可以在生成数据时保存函数的状态,从而避免占用大量内存;迭代器适用于处理少量数据,因为它需要创建一个对象维护状态。

因此,在实际开发中,我们可以根据数据量和处理效率的需求来选择使用生成器或迭代器。

2)生成器创建方式

在 Python 中,可以通过以下两种方式创建生成器:

1、通过生成器函数创建

通过在函数中使用 yield 语句,可以将函数变为生成器函数,每次调用生成器函数时,可以生成一个生成器。

例如:

def generator_example():
    yield 1
    yield 2
    yield 3

gen = generator_example()
print(next(gen))
print(next(gen))
print(next(gen))

2、通过生成器表达式创建

生成器表达式是一种简写的生成器创建方式,它基于列表推导式的语法。

例如:

gen = (x for x in range(3))
print(next(gen))
print(next(gen))
print(next(gen))

以上是生成器的两种创建方式,您可以根据实际需求选择使用。

3)生成器表达式

生成器表达式是一种简写的生成器创建方式,它基于列表推导式的语法。

例如:

gen = (x for x in range(3))
print(next(gen))
print(next(gen))
print(next(gen))

在上面的例子中,我们使用生成器表达式创建了一个生成器,该生成器生成从 0 到 2 的整数。然后,我们使用 next() 函数逐个迭代生成器中的值。

4)yield关键字

yield 关键字是 Python 中的一个关键字,用于生成器函数中。它允许一个函数在生成值时暂停其执行,以便在稍后恢复其执行并生成下一个值。这使生成器函数成为一种特殊的函数,可以按需生成一系列值。

5)生成器函数

生成器函数是 Python 中特殊的函数,该函数可生成一个生成器。与普通函数不同,生成器函数可以在每次被调用时生成一个生成器,并在生成器中生成一系列值。

生成器函数通过使用 yield 语句创建生成器。每当函数执行到 yield 语句时,生成器函数的执行就会暂停,并返回 yield 语句后面的值。当再次调用生成器函数时,它将从上次暂停的位置继续执行,直到遇到下一个 yield 语句,或者函数返回。

例如:

def generator_example():
    yield 1
    yield 2
    yield 3

gen = generator_example()
print(next(gen))
print(next(gen))
print(next(gen))

在上面的例子中,我们定义了一个生成器函数 generator_example,该函数通过使用 yield 语句生成了三个整数:1、2 和 3。然后,我们通过调用该函数并将其结果分配给生成器 gen 来创建生成器,并使用 next() 函数逐个迭代生成器中的值。

6)return 和 yield 异同

returnyield 都是用于在函数中终止执行的关键字,但是它们的作用是不同的。

  • return:当函数调用 return 时,函数立即终止执行,并返回一个值(如果存在)给调用者。该值通常表示函数的最终结果。

  • yield:当生成器函数调用 yield 时,它仅暂停其执行并生成一个值,但不终止函数。在下一次调用该生成器时,它将恢复其执行,直到遇到下一个 yield 或终止函数。

因此,yield 是生成器函数的一个关键字,可以使生成器生成一系列值,而 return 是一般函数的一个关键字,它返回一个值并终止函数

7)yield的使用方法

yield 关键字用于生成器函数。在生成器函数中,我们可以使用 yield 关键字生成一系列值,而无需暂停整个函数。

例如,以下是使用 yield 关键字的简单生成器函数的例子:

def simple_generator():
    yield 1
    yield 2
    yield 3
    yield 4
    yield 5

gen = simple_generator()
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Output: 3
print(next(gen)) # Output: 4
print(next(gen)) # Output: 5

我们可以使用 for 循环或 next() 函数迭代生成器中的值,如下所示:

for value in simple_generator():
    print(value)

# Output:
# 1
# 2
# 3
# 4
# 5

【注意】生成器函数只能迭代一次,所以请确保在使用生成器函数时存储其返回值。

8)for与next

在使用生成器时,我们可以使用两种不同的方法来迭代生成器中的值:for 循环next() 函数。

  • for 循环:通过使用 for 循环,我们可以在生成器中迭代所有值。例如:
def simple_generator():
    yield 1
    yield 2
    yield 3

for value in simple_generator():
    print(value)

# Output:
# 1
# 2
# 3
  • next() 函数:通过使用 next() 函数,我们可以手动控制生成器的迭代。例如:
def simple_generator():
    yield 1
    yield 2
    yield 3

gen = simple_generator()
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Output: 3

【注意】在生成器迭代完所有的值后,再使用 next() 函数将导致抛出 StopIteration 异常。因此,在使用 next() 函数时,请确保捕获该异常。

9)send的使用

send() 方法是生成器的一种方法,它允许我们在生成器函数内部向生成器发送数据。该方法允许生成器接收外部数据,并使用这些数据生成结果

在使用 send() 方法时,我们需要在生成器函数中使用 yield 表达式来接收数据,如下所示:

def simple_generator():
    result = yield
    print("Received data:", result)

gen = simple_generator()
next(gen)
gen.send("Hello, World!")
# Output: Received data: Hello, World!

【注意】第一次使用 send() 方法之前,我们需要调用 next() 函数,以启动生成器函数。此外,使用 send() 方法将导致抛出 StopIteration 异常,因此请确保在使用该方法时进行异常处理。

三、协程进阶

1)生成器与协程关系

生成器和协程是相关但有所不同的概念。生成器是一种特殊的迭代器,可以生成一系列的值,每次迭代时只返回一个值。生成器可以使用 yield 关键字来暂停执行,并在下一次调用时继续执行。

  • 协程是一种并发编程技术,可以在单一线程中实现多个任务的并行执行。与线程不同,协程不需要创建新的系统级线程,并且消耗的资源也比线程更少。协程可以使用 yield 关键字来暂停执行,并在下一次调用时继续执行。

  • 因此,生成器可以用于实现协程,但它们不是协程的必需条件。在 Python 中,可以使用 asyncio 库来实现协程,该库不需要使用生成器。不过,在实现协程时,生成器确实可以作为一种有效的工具,帮助开发者实现协程的暂停和恢复。

2)协程实现原理

协程的实现主要是通过一种叫做 "协程调度器" 的技术实现的,这种技术能够在不创建新的线程的情况下,在单一线程中按需切换协程的执行。协程调度器会管理当前正在运行的协程以及等待执行的协程,并在每个协程执行完后切换到下一个协程。

3)协程实现方式

在 Python 中,可以使用 asyncio 库来实现协程。协程函数可以使用 async 关键字标记,表示该函数是一个协程函数。在协程函数中,可以使用 await 关键字等待其他协程完成,从而实现协程的切换。

在 Python 中,可以使用 asyncio 库来实现协程。下面是一个简单的例子:

import asyncio

async def task1():
    print("Task 1 is running")
    await asyncio.sleep(1)
    print("Task 1 is complete")

async def task2():
    print("Task 2 is running")
    await asyncio.sleep(1)
    print("Task 2 is complete")

async def main():
    task1_coro = task1()
    task2_coro = task2()
    await asyncio.gather(task1_coro, task2_coro)

if __name__ == "__main__":
    asyncio.run(main())

上面的代码中,task1task2 是两个协程函数,它们可以在单独的任务中并行执行。main 函数是协程的入口,在这个函数中,我们创建了两个协程的对象 task1_corotask2_coro,并通过 asyncio.gather 函数将它们并行执行。最后,我们通过 asyncio.run 函数启动了整个协程。

运行上面的代码,可以得到以下输出:

Task 1 is running
Task 2 is running
Task 1 is complete
Task 2 is complete

可以看到,协程中的任务是并行执行的,因此我们可以充分利用 CPU 的资源,提高程序的执行效率。


Python 高级编程之生成器与协程进阶讲解就先到这里了,有任何疑问欢迎给我留言,后续会持续更新相关技术文章,请小伙伴耐心等待,也可以关注我的公众号【大数据与云原生技术分享】进行深入技术交流~