yield from基础概念
1.yield关键字回顾
- 生成器是Python中一种特殊的迭代器,允许函数在执行过程中保存状态 ,从而可以暂停并后续恢复执行。
- yield关键字是其核心,用于在函数中定义生成值的点。当函数遇到
yield
时 ,它会暂停执行并将控制权交还给调用者,同时返回yield
后面的值。下一次调用生成器的__next__()
方法或使用next()
函数时 ,会从上次暂停的位置继续执行。 - 例如,简单的生成器函数如下所示:
def simple_generator(): yield 1 yield 2 yield 3 gen = simple_generator() print(next(gen)) # 输出: 1 print(next(gen)) # 输出: 2 print(next(gen)) # 输出: 3
2.yield from引入动机
- 随着Python的发展 ,为了解决生成器嵌套调用时的复杂性,以及更高效地委托生成器间的数据流动,
yield from
表达式在Python 3.3中被引入。它使得一个生成器可以将控制权直接传递给另一个可迭代对象(通常是另一个生成器),并且能够透明地传递数据和异常处理。这不仅简化了代码结构 ,还提高了代码的可读性和维护性。 - 使用
yield from
之前,手动委托可能涉及循环和显式发送异常,而yield from
自动处理这些细节,使代码更加简洁。接下来的章节将会展示yield from
的具体应用与优势。
yield from深入解析 🧭
1.语法与用法
yield from
语句自Python 3.3起引入 ,它主要用作生成器间的委托,实现数据流的无缝传递。- 它提供了一种更简洁的方式来委托生成器到另一个可迭代对象,如另一个生成器、列表、元组等。这不仅简化了代码,还改善了性能 ,尤其是在处理嵌套生成器时。
- 使用
yield from
可以将一个生成器内部直接“展开”另一个生成器的所有元素,无需显式循环。 - 示例代码:
def subgenerator(): yield 'A' yield 'B' def delegating_generator(): yield from subgenerator() yield 'C' gen = delegating_generator() for value in gen: print(value)
- 输出:
A B C
2.传递生成器控制权
yield from
的关键特性在于它能够将当前生成器的控制权完全传递给右边的可迭代对象。- 这意味着在内部生成器中通过
yield
产生的值会直接传递给外部调用outer_gen()
的循环,而无需额外的循环结构。此外,如果内部生成器抛出异常,该异常也会直接传递给外部调用方,增强了错误处理的透明度。
def exception_gen(): yield from range(3) raise ValueError("This won't be raised") # 这里不会执行到 try: for value in exception_gen(): print(value) print("No exception here") # 控制流不会到达此行 except ValueError as e: print(e) # 正确捕获内部生成器的异常
3.与迭代器的高效整合
yield from
不仅限于与其他生成器的合作,它还能高效地整合任何可迭代对象,包括列表、字典、集合等。这使得编写处理复杂数据结构的生成器变得简单直观,同时保持了代码的高效率和低内存占用。- 例如,合并多个列表为一个生成器:
def combined_gen(lists): for lst in lists: yield from lst lists = [[1, 2], [3, 4], [5]] for num in combined_gen(lists): print(num) # 输出: 1, 2, 3, 4, 5
4.与传统yield对比优势
- 传统方式下,若要在生成器中迭代另一个生成器或可迭代对象,需要手动编写循环结构,如
for
循环配合yield
。 - 而yield from的优势在于:
①简化代码:直接将控制权委托给另一个生成器,无需额外循环结构。
②性能提升:在某些情况下 ,yield from
能减少调用栈的深度,提高执行效率。
③异常传递:更自然地传递异常 ,包括StopIteration
,便于错误处理。
④透明代理:使用yield from
的生成器能像直接操作原始可迭代对象一样工作,包括发送值和抛出异常的能力。
- 通过
yield from
,我们实现了生成器间的平滑过渡,简化了代码逻辑,提升了代码的可读性和维护性,同时也优化了程序的执行效率。
实战应用示例 📈
1.链接多个生成器
- 在处理复杂数据流时,往往需要将多个生成器串联起来。
yield from
提供了一种优雅的方式,能够无缝连接两个或多个生成器 ,使得数据流动自然且高效。* 示例代码:
def generator_one(): for i in range(3): yield i def generator_two(): for i in range(3, 6): yield i def combined_generator(): yield from generator_one() yield from generator_two() for number in combined_generator(): print(number)
- 输出:
0 1 2 3 4 5
- 此例展示了如何通过
yield from
轻松合并两个生成器的输出 ,形成连续的数据流。
2.简化递归生成器实现
- 递归生成器常用于处理树形或分层数据结构,如文件目录遍历。
yield from
可以显著简化递归生成器的编写,避免了显式的循环和递归调用堆栈的累加。 - 示例代码:
def traverse_dir(path='.'): for entry in os.scandir(path): if entry.is_dir(): yield from traverse_dir(entry.path) # 递归调用 else: yield entry.name for file in traverse_dir('/path/to/start/directory'): print(file)
- 通过
yield from
递归遍历目录 ,代码既简洁又高效,避免了传统递归中手动管理结果列表和回溯的复杂性。
3.复杂数据流处理
数据流基础处理
- 处理数据流时 ,我们经常需要对多个数据源进行组合或转换操作。下面是一个基础的数据流处理示例,使用生成器串联数据处理步骤:
def square_numbers(nums): for num in nums: yield num ** 2 def filter_even_squares(squares): for square in squares: if square % 2 == 0: yield square numbers = range(1, 6) squared = square_numbers(numbers) even_squares = filter_even_squares(squared) for result in even_squares: print(result) # 输出偶数平方
yield from优化数据流
- 通过
yield from
,我们可以进一步优化上述数据流处理,减少中间变量,使代码更加直观:
def combined_data_flow(nums): yield from filter_even_squares(square_numbers(nums)) for result in combined_data_flow(range(1, 6)): print(result) # 直接从复合生成器获取偶数平方结果
- 在这个例子中 ,
combined_data_flow
直接通过yield from
将两个生成器操作链接起来,简化了数据流处理的逻辑,同时保持了代码的高效和易读性。
4.错误处理与异常传递
- 在使用
yield from
的生成器中,正确处理异常尤为重要。 - 通过
yield from
,异常可以直接从被委托的生成器传播到委托生成器 ,甚至到最外层调用者,实现了清晰的错误传递路径。 - 示例代码:
def may_raise_exception(): yield 1 raise ValueError("An error occurred.") yield 2 def delegator_gen(): try: yield from may_raise_exception() except ValueError as e: print(f"Caught an exception: {e}") for value in delegator_gen(): print(value)
- 输出:
1 Caught an exception: An error occurred.
- 此例说明了如何在委托生成器中捕获并处理来自被委托生成器的异常,保证了程序的健壮性。
yield from在协程中的角色 🔄
1.协程基础与async/await
- 协程是一种在单线程内实现并发执行的技术,它允许程序在执行过程中挂起并在之后恢复。
- 在Python中,从3.5版本开始,
async/await
语法成为实现协程的主要方式 ,替代了早期基于yield from
的协程实现。尽管如此,理解yield from
对于掌握Python异步编程的底层机制仍然重要。
async/await简介:
-
•
async def
定义了一个异步函数 ,该函数在调用时不会立即执行,而是返回一个协程对象。 -
•
await
关键字用于等待一个异步操作完成 ,如等待网络请求或文件读取等。
2.yield from与asyncio结合使用
- 虽然现代异步编程倾向于使用
async/await
,但在某些特定场景或向后兼容的代码中,yield from
仍可能与asyncio
框架一同出现。特别是处理遗留代码时,理解两者如何协作至关重要。 -
- 示例代码(注意:此例展示的是历史背景下yield from的用法 ,当前推荐使用async/await):
import asyncio @asyncio.coroutine def legacy_coroutine(): yield from asyncio.sleep(1) print("Hello from legacy coroutine") async def main(): task = asyncio.ensure_future(legacy_coroutine()) await task loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) finally: loop.close()
- 此例展示了如何在一个基于
asyncio
的异步环境中使用带有yield from
的遗留协程。
3.异步IO处理实例理解yield from(以及现代的async/await)对于处理异步IO操作至关重要,比如并发网络请求或文件操作,可以显著提升应用的响应速度和资源利用率。
- async/await风格的异步IO示例:
import aiohttp import asyncio async def fetch_url(session, url): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: html = await fetch_url(session, 'https://example.com') print(html[:100]) loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) finally: loop.close()
- 这段代码展示了如何使用
asyncio
和aiohttp
库并发地从网站获取网页内容 ,体现了异步IO处理的强大能力。虽然未直接使用yield from
,但它背后的概念—即非阻塞的迭代和控制流委托—仍然是现代异步编程模型的核心。
总结与展望 🚀
- 探索
yield from
的核心价值在于其对Python生成器的强化能力,通过简化委托生成器与迭代器的集成,实现高效的数据流控制与传递。这一特性从根本上优化了代码结构,提升程序的可读性和执行效率,尤其在处理复杂数据遍历、文件系统遍历及异步IO等场景中展现出独特优势。虽然现代异步编程倾向于使用async/await
,但理解yield from
对于深入Python异步机制及历史演变至关重要。性能与错误处理方面 ,它通过减少栈深度和透明化异常传递,提供了调试便利与内存使用的灵活性。对比其他语言如Java的Iterable,Python的yield from
在组合迭代器方面显得更为简洁高效。-
最后
学习资料已打包,需要的小伙伴可以戳这里[学习资料]或扫描下方码!!!!!!!