yield
是 Python 关键字之一,用于创建一个生成器(Generator)。生成器是迭代器(Iterator)的一种。这里将会记录一些关于生成器与迭代器的东西。
迭代器
迭代器是访问集合内元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。
迭代器不能回退,只能往前进行迭代。这并不是什么很大的缺点,因为人们几乎不需要在迭代途中进行回退操作。
迭代器也不是线程安全的,在多线程环境中对可变集合使用迭代器是一个危险的操作。但如果小心谨慎,或者干脆贯彻函数式思想坚持使用不可变的集合,那这也不是什么大问题。
对于原生支持随机访问的数据结构(如 tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()
找回这个索引值,这是后话)。但对于无法随机访问的数据结构(比如 set)而言,迭代器是唯一的访问元素的方式。
迭代器的另一个优点就是它不要求你事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个 GB 的文件,或是斐波那契数列等等。这个特点被称为延迟计算或惰性求值(Lazy evaluation)。
迭代器更大的功劳是提供了一个统一的访问集合的接口。只要是实现了__iter__()
方法的对象,就可以使用迭代器进行访问。
迭代器的创建与使用很简单,如下:
1 | range(3) lst = |
实际上,自定义容器对象只需要拥有__iter__()
方法,将迭代操作代理到容器内部对象上去,那么当将该对象当作参数被工厂函数iter()
调用后,iter()
的返回值就是个可迭代对象。使用next()
访问迭代器的下一个元素。可以这么理解:iter(obj)
调用了对象 obj 的obj.__iter__()
方法,next(obj)
调用了obj.__next__()
方法。语法糖for
干了这么几件事:自动调用工厂函数iter()
获得迭代器,自动调用next()
获取元素,还完成检查 StopIteration 异常的工作。
tips:itertools 模块实现了许多迭代功能,当碰到看上去有些复杂的迭代问题时,不妨先去看看 itertools 模块。
生成器
生成器是迭代器,同时也并不仅仅是迭代器,不过迭代器之外的用途实在是不多,所以我们可以大声地说:生成器提供了非常方便的自定义迭代器的途径。
- yield 可以使函数返回一个生成器。这种方法被对象中的
__iter__
与__next__
定义。 - 生成器使迭代器的协议得以实现,因此你可以使用迭代的方法遍历生成器。不同于迭代器,一旦计算,生成器是不可能回复或重置的(迭代器可以通过索引或其他方法再次计算)。
- 生成器可以 send information, 可以让生成器成为概念上的协程。
- 在 Python3 中,你可以使用
yield from
关键字双向 delegate 一个生成器到另一个生成器。
上面的四条到底是神马!怎么看着晕乎乎的。:D真不好意思,这是我翻译的_我自己第一眼看到也是同样苦恼,不过没关系,先看看我参考的资料呗:
- 当然首先推荐这个啦:What does the yield keyword do in Python?(置顶的回答)。我猜能玩全看懂的不多。
- 那就看看这个:Python函数式编程指南(四):生成器
- 想了解第二条的含义,看看这两篇:python3-cookbook 4.2 代理迭代、python3-cookbook 4.3 使用生成器创建新的迭代模式
能把前两个看一半,最后一个看完,我相信前两条一定可以明白的。我在下面写一写我当初的困惑:
- 函数中有多个
yield
,next()
是啥情况 - next(generator)执行到哪里(这点很重要噢)
result = yield
这个表达式的含义(拜托,不要质疑,没少写,这是来源:python3-cookbook 7.10 带额外状态信息的回调函数。不相信?俺人好:Python Cookbook:Carrying Extra State with Callback Functions,“啪啪啪”)
第一个很简单,我就懒得写了。关于第二个,先看看生成器的三个方法:
send()
:简单地说,next()
方法可以恢复生成器状态并继续执行,send()
是除next()
外另一个恢复生成器的方法。在 Python3 中,yield
语句是个表达式,那么表达式可以有一个值,这个值就是send(msg)
所传递的 msg。send(None)
(或者说是send()
)等价与next()
,这时候yield
表达式的值嘛,猜呗^^ 下面是个栗子:
1 | def foo(): |
剩下两个方法:close()
——这个方法用于关闭生成器,对关闭的生成器后再次调用next()
或send(msg)
将抛出 StopIteration 异常。throw()
—— 可以引发任何类型的异常。在这里就不过多说了(其实我也不懂T_T)。到这里已经结束第二个问题了。纳尼!答案呢?我鄙视你,自己看栗子。
第三个嘛,下面是我自己“猜”的,若有错误欢迎指证:
1 | def foo1(): |
So easy?到这里,是不是更清晰了呢?send infomation 是不是也明白了呢?下面是关于yield from
的,困扰了我好久。Ready? Go!
我又懒了,又想让你戳屏幕了(_)
- Python3中的yield from语法 PEP-380 这一段很赞!
- python3-cookbook 4.4 实现迭代器协议 找张纸,拿支笔,慢慢琢磨。
- 现在看看What does the yield keyword do in Python?,是不是很清晰了?
- →_→ 看不懂,我的理解在下面。
我在这里只能写(抄)一写(抄)目前的拙见:
1 | def A(): |
B()
返回的是一个可迭代对象 b,那么A()
会返回一个 generator ——暂且叫 a 吧。
- b 迭代产生的每个值都直接传递给 a 的调用者。
- 所有通过
send(msg)
发送到 a 的值都被直接传递给 b。若send(None)
,则执行 b 的__next__()
方法。 - 若对 b 的方法调用(
send(msg)
或__next__()
方法)产生 StopIteration 异常,a 会继续执行yield from
后面的语句;而其他异常则会传播到 a,导致 a 在执行yield from
的时候抛出异常。 - 若除 GeneratorExit 以外的异常被
throw
到 a 中,该异常会被直接throw
到 b 中。bthrow
方法产生的任何异常,a 的行为同对 b 的方法调用产生的异常。 - 如果 GeneratorExit 异常被
throw
到 a 中,或者 a 的close
方法被调用,若 b 也有close
方法,bclose
方法也会被调用。b 的close
方法抛出异常,则会导致 a 也抛出异常。若 b 成功close
,a 会抛出 GeneratorExit 异常。 - a 中
yield from
表达式的求值结果是 b 迭代结束时抛出的 StopIteration 异常的第一个参数。 - b 中的
return <expr>
语句实际上会抛出 StopIteration 异常,b 中return
的值会成为a中yield from
表达式的值
上面七句话说了这么个意思:
- 1、2说明 a 与 b 之间
send(msg)
的双向 delegate 以及 a 与 b 的行为 - 3、4、5说明异常的双向 delegate 以及 a 与 b 的行为
- 6、7说明了
yield from
的表达式的值是什么
接下来看那几段代码:
1 | def money_manager(expected_rate): |
第二个。嗨!代码太长了,都深夜了,明天还得早起,不干了,自己多画画:>好运!
参考资料
Python函数式编程指南(三):迭代器
Python函数式编程指南(四):生成器
python3-cookbook 4.2 代理迭代
python3-cookbook 4.3 使用生成器创建新的迭代模式
python3-cookbook 4.4 实现迭代器协议
python3-cookbook 7.10 带额外状态信息的回调函数
What does the yield keyword do in Python?
Python3中的yield from语法