扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
当fib()函数第一次调用时,他设定a=0,b=1,于是yield b返回到调用者,调用者看到的是1,当fib继续时,从它的观点来看,yield语句与打印语句实际上是一样的:yield 执行后,所有的局部变量都没有改变,fib继续。a和b就会变成1和1,并且fib循环回到yield调用,将1赋给它的调用程序。以此类推。从fib的观点来看,它仅仅是传递了结果的顺序,就好像借助于回调程序一样。但是从调用者的观点来看,fib 调用是一个任意重新开始的可迭代的对象。 在线程方式中,这就允许两边都以自然的方式编码;但是与线程方式不同的是,它可以更有效并且可工作于所有的平台。实际上,重新开始一个GENERATOR应该和一个函数调用占用差不多的资源。
同样一种方式应用在多生产者/消费者函数上。例如:tokenize.py可以赋值给下一个令牌,而不是将一个回调函数作为参数调用,并且tokenize客户端函数能够用一种自然的方式迭代令牌;一个Python GENERATOR是一个Python迭代器[1],而且相当功能强大。
说明: Yield
一个新的语句是这样引入的:
yield_stmt: "yield" expression_list
"yield"语句是一个新的关键词,所有future语句[8]都需要经历下列阶段:在初始发布时,需要使用GENERATOR的模块必须包括下划线:
from __future__ import generators
并放在靠近开头的部分(具体细节请看 PEP 236[8]))。不用一个future语句就使用标志符"yield"的模块会引起警告的。在接下来的发布中,"yield"会成为一种语言的关键词,而且future语句不需要了。
yield语句只能用于操作函数内部。包括一个yield语句的操作函数称为GENERATOR操作函数。GENERATOR操作函数在各个方面都是一个普通的操作函数,但是代码对象的co_flags成员中拥有新的CO_GENERATOR标记设置。
当一个GENERATOR函数被调用时,实参以一个通用的方式与局部操作函数的形参对应,但是在该操作函数的体中没有执行代码。它不再返回一个GENERATOR-迭代器对象,而是遵守迭代器协议[6],所以在特殊情况下可以一种自然的方式应用在for循环语句中。记住:当上下文含义清楚时,绝对的名字"generator"要么指代一个GENERATOR操作函数,要么指代GENERATOR-迭代器。
每一次调用一个GENERATOR-迭代器的.next()函数时,GENERATOR操作函数的体中的编码会一直执行直到碰到一个yield语句或者返回语句(参看上文)为止,再就是到达体的结尾。
如果碰到yield语句的话,操作函数的状态被冻结,并且返回参数列表的值到.next()的调用处。冻结的意思是所有的状态都被保持,包括局部变量的当前捆绑、指令指针、和内部计算堆栈:保存了足够的信息以便下一次调用.next()时,该操作函数可以准确地执行,就好像yield语句只是外部的一个调用而已。
限制:yield语句不允许出现在一个try/finally结构的一个try子句中。问题是无法保证GENERATOR什么时候可重新开始,因此也无法保证finally块什么时候执行;那是对finally用途违背了太多了。
限制:在GENERATOR正在运行时不得重新开始:
>>> def g():
... i = me.next()
... yield i
>>> me = g()
>>> me.next()
Traceback (most recent call last):
...
File "<string>", line 2, in g
ValueError: generator already executing
说明:返回
一个GENERATOR操作函数也可以包括下列形式的返回语句:
"return"
记住:参数列表不允许用在GENERATOR体中的返回语句上(尽管,他们可以嵌套在GENERATOR的非GENERATOR操作函数的体上)。
当遇到一个返回语句时,控制进程像其他函数一样的返回,执行正确的finally子句(如果有的话)。当产生一个StopIteration异常,表示该迭代器已经耗尽,如果控制流程脱离GENERATOR末端而没有一个清楚地返回的话也会产生StopIteration异常。
记住:返回的意思是"我执行完了,并且没有带回任何你所关心的事情",这对于GENERATOR操作函数与非GENERATOR操作函数是一样的。
注意:返回并不总是等同于产生StopIteration:他们的差别在于如何处理封装try/except结构。
例如:
>>> def f1():
... try:
... return
... except:
... yield 1
>>> print list(f1())
[]
因为,和其他操作函数一样,返回仅仅是退出,但是:
>>> def f2():
... try:
... raise StopIteration
... except:
... yield 42
>>> print list(f2())
[42]
因为StopIteration是由一个空的"except"捕捉的,这和其它的异常相同。
说明:GENERATOR和异常传播
如果一个未处理的异常--包括,但是并不只限于StopIteration--由一个GENERATOR操作函数产生,或者通过它传递。这样异常会以一种通常的方式传递到调用者处。试图并发重新开始GENERATOR操作函数会引起StopIteration。换句话说,一个未处理的异常会终止了一个GENERATOR的有效生命周期。
例如(不合乎语言习惯但是可以解释这一观点):
>>> def f():
... return 1/0
>>> def g():
... yield f() # the zero division exception propagates
... yield 42 # and we'll never get here
>>> k = g()
>>> k.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in g
File "<stdin>", line 2, in f
ZeroDivisionError: integer division or modulo by zero
>>> k.next() # and the generator cannot be resumed
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
>>>
说明:Try/Except/Finally
就像上文指出的一样,yield语句不允许出现在一个try/finally结构的一个try子句中。结果是GENERATOR应该非常小心地分配关键性的资源。对于yield语句出现在finally子句、except 子句、 或者在try/except结构的 try 子句中都没有限制:
>>> def f():
... try:
... yield 1
... try:
... yield 2
... 1/0
... yield 3 # never get here
... except ZeroDivisionError:
... yield 4
... yield 5
... raise
... except:
... yield 6
... yield 7 # the "raise" above stops this
... except:
... yield 8
... yield 9
... try:
... x = 12
... finally:
... yield 10
... yield 11
>>> print list(f())
[1, 2, 4, 5, 8, 9, 10, 11]
>>>
范例
# A binary tree class.
class Tree:
def __init__(self, label, left=None, right=None):
self.label = label
self.left = left
self.right = right
def __repr__(self, level=0, indent=" "):
s = level*indent + `self.label`
if self.left:
s = s + "\n" + self.left.__repr__(level+1, indent)
if self.right:
s = s + "\n" + self.right.__repr__(level+1, indent)
return s
def __iter__(self):
return inorder(self)
# Create a Tree from a list.
def tree(list):
n = len(list)
if n == 0:
return []
i = n / 2
return Tree(list[i], tree(list[:i]), tree(list[i+1:]))
# A recursive generator that generates Tree labels in in-order.
def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
# Show it off: create a tree.
t = tree("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
# Print the nodes of the tree in in-order.
for x in t:
print x,
print
# A non-recursive generator.
def inorder(node):
stack = []
while node:
while node.left:
stack.append(node)
node = node.left
yield node.label
while not node.right:
try:
node = stack.pop()
except IndexError:
return
yield node.label
node = node.right
# Exercise the non-recursive generator.
for x in t:
print x,
print
两者的输出块显示为:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
问题 & 解答
问题:为什么不用一个新的关键字来代替重复使用的"def"?
解答:请看下面的BDFL部分。
问题:为什么用一个新的关键字表示"yield"?为什么不用一个已经存在的函数代替?
解答:控制流程在Python语言中通过关键字表达要好得多,并且yield是一个控制结构。我们也相信,用Jython语言有效的实际过程需要编译器在编译时间内能够判断潜在的断点,而且一个新的关键字使得此过程容易得多。CPython参考实现过程也要大力的开发它,为了判断哪种操作函数是GENERATOR操作函数(尽管用一个新的关键字代替"def"会替CPython解决此问题--但是人们会问"为什么用一个新的关键字呢",它不需要任何新的关键词)。
问题:那么为什么一些其他特殊的语法不用新的关键字呢?例如,其中一个都可以代替"yield 3":
return 3 and continue
return and continue 3
return generating 3
continue return 3
return >> , 3
from generator return 3
return >> 3
return << 3
>> 3
<< 3
* 3
解答:我错过了一个<wink>?在上百条信息中,我选择了三种建议作为备选项,并且是从它们中抽取了上述几条。不需要一个新的关键字是可以的,但是让yield非常清楚却是更好的选择--我并不想去*推论*:一个yield的存在并不仅是让关键字或者操作器以前毫无意义的顺序变得有意义。当然,如果这吸引了足够的兴趣的话,那么建议者应该决定一个一致同意的建议,并且Guido会宣告这一点。
问题:为什么总是允许"return"?为什么不强制终止拼写成"raise StopIteration"?
解答: StopIteration的机制是低水平的细节,它与Python 2.1中的IndexError的机制相像:实现过程需要在开头很好地定义*something*。并且Python将这些机制对高级用户开放。但是,那不是为了强迫每个人都在这个等级上工作而设定的一个参数。"return"在任何一个操作汉书中都意味着"我已经完成了",并且那是易于理解和使用的。记住:在try/except结构中,"return"并不总是等同于"raise StopIteration"(参看"说明:返回"部分)。
问题:那么为什么不允许"return"语句加上表达式呢?
解答:也许有一天我们会允许的。在Icon语言中"return expr"既意味着"我已经完成了",也表示"但是我有一个最终有用的值返回,就是这个值"的意思。在开始阶段,缺乏"return expr"的强制使用,对于值的传送来说,只使用"yield"要简单清楚的多。
BDFL 声明
提议方:介绍另一种新的关键字("gen" 或者"generator")取代"def",或者其他的备选语法,从而区分GENERATOR操作函数与非GENERATOR操作函数。
反对方:实际上(不管你怎么认为),GENERATOR属于操作函数,但是他们稍做了变动,他们是可重启的。他们如何启动的机制是相对较小的技术难题,并且引入一个新的关键字不会帮助全面强调GENERATOR如何启动的机制(GENERATOR的生命中的一个关键却又微小的部分)。
专家:实际上(不管你怎么认为),GENERATOR操作函数是一个真正的库函数。它魔术似的产生GENERATOR-迭代器。在这个方面,他们与非GENERATOR操作函数有着本质的区别,他们更像一个结构而不仅仅是一个操作函数,所以重复使用"def"是最好的选择。藏于体中的"yield"并不足以警告:该部分是如此的与众不同。
BDFL:"def"它保留下来,任何一方都没有完全令人信服的论据,所以我咨询了我的语言设计者。它告诉我们:在PEP中假设的语法是相当正确的--既不太热,也不太冷。但是,像在希腊神话中特尔斐的预言一样,它不会告诉我们为什么,所以我也不会为PEP的争论辩驳。我所能提出的(远离辩驳...已经做过)最好就是"FuD"。一旦某天成为语言的一部分,我非常怀疑它是否抄袭了Andrew Kuchling的 《Python 瑕疵》一文。
参考实现过程
当前的实现,处在初始阶段(没有文档,但是测试良好又可靠)。它是Python CVS开发树[9]的一部分。它要求你从资源中建立Python语言。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者