鱼C论坛

 找回密码
 立即注册
查看: 2596|回复: 2

[技术交流] Python 小技巧 075:Python 的 yield from

[复制链接]
发表于 2020-4-11 18:48:18 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 zltzlt 于 2020-4-11 18:48 编辑

Python yield from


yield from 是 Python 3.3 新增的语法,这篇帖子将会介绍他的基本用法和高级用法。

一、yield from 的简单用法

它可以替代内层的 for 循环。如果一个生成器需要生成另一个生成器生成的值,有两种方法:

1. 使用 for 循环

    例如:

   
  1. >>> def gen():
  2.         for i in range(5):
  3.                 yield i

  4.                
  5. >>> list(gen())
  6. [0, 1, 2, 3, 4]
复制代码


2. 直接使用 yield from

    例如:

   
  1. >>> def gen():
  2.         yield from range(5)

  3.         
  4. >>> list(gen())
  5. [0, 1, 2, 3, 4]
复制代码


yield from 的原理是,先获取需要 “yield from” 的迭代器,然后遍历这个迭代器,yield 它的每一个值,直到迭代器抛出 StopIteration。

利用 yield from 可以实现 itertools 的 chain 生成器:

  1. >>> def chain(*iterables):
  2.         for i in iterables:
  3.                 yield from i


  4. >>> list(chain(range(3), range(25, 30), range(90, 100, 2)))
  5. [0, 1, 2, 25, 26, 27, 28, 29, 90, 92, 94, 96, 98]
复制代码


二、yield from 的高级用法

当然,虽然 yield from 是 yield 的改进版,但是如果仅有上面一点点作用,Python 之父大可不必将它添加进 Python 语法里。

yield from 还改进了 yield 的一些缺点,例如 yield 无法获取生成器 return 的返回值。

我们都知道,在遍历一个生成器并 yield 它的每一个元素时,如果使用 for 语句去迭代生成器,则不会显式地触发 StopIteration 异常,而是自动捕获 StopIteration 异常。

所以如果遇到 return,只会终止迭代,而不会触发异常,因此也就没办法获取 return 的值。

例如:

  1. def gen():
  2.     for i in [3, 5, 7, 9, 8, 3, 6]:
  3.         if i % 2 == 0:    # 遇到第一个偶数强制退出
  4.             return '我被迫退出了'
  5.         yield i


  6. for i in gen():
  7.     print(i)
复制代码


打印的是 3 5 7 9,迭代到 8 的时候生成器 return 时隐式地触发了 StopIteration 异常,但是 for 自动捕捉了这个异常并且退出了循环。

但是使用 next(generator) 一次一次地迭代就会触发 StopIteration 异常,这时我们就可以捕捉并打印生成器的返回值了:

  1. def gen():
  2.     for i in [3, 5, 7, 9, 8, 3, 6]:
  3.         if i % 2 == 0:    # 遇到第一个偶数强制退出
  4.             return '我被迫退出了'
  5.         yield i


  6. g = gen()
  7. while True:
  8.     try:
  9.         print(next(g))
  10.     except StopIteration as e:
  11.         print("value:", e.value)
  12.         break
复制代码


执行效果:

  1. 3
  2. 5
  3. 7
  4. 9
  5. value: 我被迫退出了
复制代码


现在我们用 yield from 实现同样的功能:

  1. def gen():
  2.     for i in [3, 5, 7, 9, 8, 3, 6]:
  3.         if i % 2 == 0:  # 遇到第一个偶数强制退出
  4.             return '我被迫退出了'
  5.         yield i


  6. def wrap_gen(generator):  # 定义一个包装生成器的生成器,它的本质还是生成器
  7.     result = yield from generator  # 自动触发 StopIteration 异常,并且将 return 的返回值赋值给 yield from 表达式的结果,即 result
  8.     print("result:", result)


  9. def main():
  10.     for j in wrap_gen(gen()):
  11.         print(j)


  12. if __name__ == '__main__':
  13.     main()
复制代码


执行结果:

  1. 3
  2. 5
  3. 7
  4. 9
  5. result: 我被迫退出了
复制代码


可以看出 yield from 具有以下的特点:

yield from iterable 结构会在内部自动捕获生成器的 StopIteration 异常。

这种处理方式与 for 循环处理 StopIteration 异常的方式一样。

而且对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把 return 返回的值或者是 StopIteration 的 value 属性的值变成 yield from 表达式的值,即上面的 result。

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2020-4-27 13:20:34 | 显示全部楼层
学习了!
话说下一次更新是什么时候???
@zltzlt
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-8-1 15:08:23 | 显示全部楼层
催更
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-6-24 16:04

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表