在Python中使用yield
关键字有什么用?它有什么作用?
例如,我正在尝试理解这个代码1:
def _get_child_candidates(self, distance, min_dist, max_dist):
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
这是来电者:
result, candidates = [], [self]
while candidates:
node = candidates.pop()
distance = node._get_dist(obj)
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
当调用方法_get_child_candidates
时会发生什么?列表是否返回?单个元素?它又被召唤了吗?后续通话何时停止?
这段代码是由Jochen Schulz(jrschulz)编写的,他为度量空间创建了一个很棒的Python库。这是完整来源的链接:Module mspace。
要了解yield
的作用,您必须了解生成器是什么。在您了解生成器之前,您必须了解可迭代物。
创建列表时,您可以逐个阅读其项目。逐个读取它的项称为迭代:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
是一个可迭代的。当您使用列表推导时,您创建一个列表,因此是一个可迭代的:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
您可以使用“for... in...
”的所有内容都是可迭代的; lists
,strings
,文件......
这些迭代很方便,因为您可以根据需要阅读它们,但是您将所有值存储在内存中,当您拥有大量值时,这并不总是您想要的。
生成器是迭代器,是一种只能迭代一次的迭代器。生成器不会将所有值存储在内存中,它们会动态生成值:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
除了你使用()
而不是[]
之外,它是一样的。但是,你不能再次执行for i in mygenerator
,因为生成器只能使用一次:它们计算0,然后忘记它并计算1,并逐个结束计算4。
yield
是一个像return
一样使用的关键字,除了函数将返回一个生成器。
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
这是一个无用的例子,但是当你知道你的函数将返回一组你只需要阅读一次的大量值时它会很方便。
要掌握yield
,您必须明白,当您调用该函数时,您在函数体中编写的代码不会运行。该函数只返回生成器对象,这有点棘手:-)
然后,每次for
使用生成器时,您的代码将从它停止的位置继续。
现在困难的部分:
第一次for
调用从函数创建的生成器对象时,它将从头开始运行函数中的代码,直到它达到yield
,然后它将返回循环的第一个值。然后,每个其他调用将再次运行您在函数中写入的循环,并返回下一个值,直到没有值返回。
一旦该函数运行,该生成器被认为是空的,但不再触及yield
。这可能是因为循环已经结束,或者因为你不再满足"if/else"
。
发电机:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
呼叫者:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidates list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
此代码包含几个智能部分:
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
耗尽了生成器的所有值,但是while
不断创建新的生成器对象,这些对象将生成与之前的值不同的值,因为它不应用于同一节点。extend()
方法是一个列表对象方法,它需要一个iterable并将其值添加到列表中。通常我们将列表传递给它:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
但是在你的代码中它得到了一个生成器,这很好,因为:
它的工作原理是因为Python不关心方法的参数是否是列表。 Python期望iterables所以它将适用于字符串,列表,元组和生成器!这叫做鸭子打字,这也是Python如此酷的原因之一。但这是另一个故事,另一个问题......
你可以在这里停下来,或者阅读一下看看发电机的高级用途:
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
注意:对于Python 3,请使用print(corner_street_atm.__next__())
或print(next(corner_street_atm))
它可用于控制对资源的访问等各种事物。
itertools模块包含操作iterables的特殊函数。曾经希望复制一台发电机?链两个发电机?使用单行分组嵌套列表中的值? Map / Zip
没有创建另一个列表?
然后只是import itertools
。
一个例子?让我们来看看四匹马比赛的可能到达顺序:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
迭代是一个暗示迭代(实现__iter__()
方法)和迭代器(实现__next__()
方法)的过程。 Iterables是您可以从中获取迭代器的任何对象。迭代器是允许您迭代迭代的对象。
在这篇关于how for
loops work的文章中有更多关于它的内容。
产量为您提供发电机。
def get_odd_numbers(i):
return range(1, i, 2)
def yield_odd_numbers(i):
for x in range(1, i, 2):
yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5
如您所见,在第一种情况下,foo
会立即将整个列表保存在内存中。对于包含5个元素的列表来说,这不是什么大问题,但是如果你想要一个500万的列表呢?这不仅是一个巨大的内存消耗者,而且在调用函数时也需要花费大量时间来构建。
在第二种情况下,bar
只给你一个发电机。生成器是可迭代的 - 这意味着您可以在for
循环等中使用它,但每个值只能被访问一次。所有值也不会同时存储在内存中;生成器对象“记住”上次调用它时循环的位置 - 这样,如果你使用一个可迭代(比方说)计数到500亿,你就不必数到500亿全部立即存储500亿个数字。
同样,这是一个非常人为的例子,如果你真的想要数到500亿,你可能会使用itertools。 :)
这是生成器最简单的用例。正如你所说,它可以用来编写有效的排列,使用yield来通过调用堆栈推送,而不是使用某种堆栈变量。生成器也可以用于专门的树遍历,以及其他各种方式。
它正在返回一台发电机。我对Python并不是特别熟悉,但我相信如果你熟悉那些它就和C#'s iterator blocks一样。
关键的想法是编译器/解释器/无论做什么都有一些技巧,所以就调用者而言,他们可以继续调用next()并且它将保持返回值 - 就好像生成器方法被暂停一样。现在显然你不能真正“暂停”一个方法,所以编译器会建立一个状态机,让你记住你当前的位置以及局部变量等。这比自己编写迭代器容易得多。
在描述如何使用发电机的许多重要答案中,有一种我认为尚未给出的答案。这是编程语言理论的答案:
Python中的yield
语句返回一个生成器。 Python中的生成器是一个返回continuation的函数(特别是一种coroutine,但continuation代表了理解正在发生的事情的更通用的机制)。
编程语言理论的延续是一种更为基础的计算,但它们并不经常使用,因为它们极难推理并且也很难实现。但是延续的概念是直截了当的:它是尚未完成的计算状态。在此状态下,将保存变量的当前值,尚未执行的操作等。然后在程序的某个时刻,可以调用continuation,以便程序的变量重置为该状态,并执行保存的操作。
以这种更一般的形式,可以以两种方式实现继续。在call/cc
方式中,程序的堆栈实际上是保存的,然后在调用continuation时,堆栈将被恢复。
在连续传递样式(CPS)中,continuation只是普通函数(仅在函数是第一类的语言中),程序员明确地管理它并传递给子例程。在这种风格中,程序状态由闭包(以及恰好在其中编码的变量)表示,而不是驻留在堆栈中某处的变量。管理控制流的函数接受继续作为参数(在CPS的某些变体中,函数可以接受多个延续)并通过简单地调用它们并在之后返回来调用它们来操纵控制流。延续传递风格的一个非常简单的例子如下:
def save_file(filename):
def write_file_continuation():
write_stuff_to_file(filename)
check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)
在这个(非常简单的)示例中,程序员将实际写入文件的操作保存到延续中(这可能是一个非常复杂的操作,需要写出许多细节),然后传递该延续(即,作为第一个 - class closure)到另一个执行更多处理的运算符,然后在必要时调用它。 (我在实际的GUI编程中经常使用这种设计模式,因为它节省了我的代码行,或者更重要的是,在GUI事件触发后管理控制流。)
本文的其余部分将不失一般性地将延续概念化为CPS,因为它更容易理解和阅读。
现在让我们来谈谈Python中的生成器。生成器是延续的特定子类型。虽然continuation通常能够保存计算状态(即程序的调用堆栈),但生成器只能在迭代器上保存迭代状态。虽然这个定义对于某些发电机的使用情况略有误导。例如:
def f():
while True:
yield 4
这显然是一个合理的迭代,其行为很明确 - 每次生成器迭代它,它返回4(并且永远这样做)。但是在考虑迭代器(即for x in collection: do_something(x)
)时,它可能不是想到的典型迭代类型。这个例子说明了生成器的强大功能:如果有任何东西是迭代器,那么生成器可以保存它的迭代状态。
重申:Continuations可以保存程序堆栈的状态,生成器可以保存迭代状态。这意味着continuation比生成器更强大,但生成器也很多,更容易。它们对于语言设计者来说更容易实现,并且程序员更容易使用它们(如果你有时间刻录,尝试阅读和理解this page about continuations and call/cc)。
但是您可以轻松地实现(和概念化)生成器作为连续传递样式的简单,特定情况:
每当调用yield
时,它会告诉函数返回延续。当再次调用该函数时,它从它停止的任何地方开始。因此,在伪伪代码(即,不是伪代码,而不是代码)中,生成器的next
方法基本如下:
class Generator():
def __init__(self,iterable,generatorfun):
self.next_continuation = lambda:generatorfun(iterable)
def next(self):
value, next_continuation = self.next_continuation()
self.next_continuation = next_continuation
return value
其中yield
关键字实际上是真正的生成器函数的语法糖,基本上类似于:
def generatorfun(iterable):
if len(iterable) == 0:
raise StopIteration
else:
return (iterable[0], lambda:generatorfun(iterable[1:]))
请记住,这只是伪代码,Python中生成器的实际实现更复杂。但是作为理解发生了什么的练习,尝试使用延续传递样式来实现生成器对象而不使用yield
关键字。
这是一个简单语言的例子。我将提供高级人类概念与低级Python概念之间的对应关系。
我想操作一系列数字,但我不想因为创建该序列而烦扰我自己,我只想专注于我想要做的操作。所以,我做了以下事情:
def
ining生成器函数,即包含yield
的函数。.next()
。StopIteration
异常生成器函数不需要引发异常。当函数结束或发出return
时它会自动引发。这就是生成器的作用(包含yield
的函数);它开始执行,每当它执行yield
时暂停,当被要求获得.next()
值时,它会从最后一点继续。它完全符合Python的迭代器协议的设计,它描述了如何顺序请求值。
迭代器协议最着名的用户是Python中的for
命令。所以,每当你做一个:
for item in sequence:
如果sequence
是如上所述的列表,字符串,字典或生成器对象,则无关紧要;结果是一样的:你逐个读取序列中的项目。
请注意,def
ining包含yield
关键字的函数不是创建生成器的唯一方法;这只是创建一个最简单的方法。
有关更准确的信息,请阅读Python文档中的iterator types,yield statement和generators。
虽然很多答案显示了为什么你使用yield
创建一个生成器,但yield
有更多的用途。制作协程非常容易,它可以在两个代码块之间传递信息。我将不再重复使用yield
创建生成器的任何精细示例。
为了帮助理解yield
在以下代码中的作用,您可以用手指在任何具有yield
的代码中跟踪循环。每当你的手指击中yield
时,你必须等待输入next
或send
。当调用next
时,你会遍历代码直到你点击yield
... yield
右边的代码被评估并返回给调用者......然后你等待。当再次调用next
时,您将通过代码执行另一个循环。但是,您会注意到在协程中,yield
也可以与send
一起使用...它将从调用者发送一个值到屈服函数。如果给出了send
,则yield
接收发送的值,并将其吐出左侧...然后通过代码的跟踪进行直到您再次击中yield
(返回结束时的值,就像调用next
一样)。
例如:
>>> def coroutine():
... i = -1
... while True:
... i += 1
... val = (yield i)
... print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
还有另一个yield
用法和含义(自Python 3.3起):
yield from <expr>
来自PEP 380 -- Syntax for Delegating to a Subgenerator:
提出了一种语法,用于生成器将其部分操作委托给另一个生成器。这允许将包含'yield'的代码段分解出来并放在另一个生成器中。此外,允许子生成器返回一个值,该值可供委派生成器使用。
当一个生成器重新生成另一个生成器生成的值时,新语法也会为优化提供一些机会。
此外this将介绍(自Python 3.5):
async def new_coroutine(data):
...
await blocking_action()
避免协同程序与常规生成器混淆(今天使用yield
)。
所有伟大的答案,但新手有点困难。
我假设你已经学会了return
声明。
作为类比,return
和yield
是双胞胎。 return
的意思是'返回和停止',而'yield`意味着'返回,但继续'
- 尝试使用
return
获取num_list。
def num_list(n):
for i in range(n):
return i
运行:
In [5]: num_list(3)
Out[5]: 0
看,你只得到一个数字而不是它们的列表。 return
永远不会让你高兴,只执行一次并退出。
- 来自
yield
用return
替换yield
:
In [10]: def num_list(n):
...: for i in range(n):
...: yield i
...:
In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>
In [12]: list(num_list(3))
Out[12]: [0, 1, 2]
现在,你赢了所有数字。
与运行一次并停止的return
相比,yield
运行您计划的时间。您可以将return
解释为return one of them
,将yield
解释为return all of them
。这叫做iterable
。
- 还有一步我们可以用
yield
重写return
语句
In [15]: def num_list(n):
...: result = []
...: for i in range(n):
...: result.append(i)
...: return result
In [16]: num_list(3)
Out[16]: [0, 1, 2]
这是yield
的核心。
列表return
输出和对象yield
输出之间的区别是:
您将始终从列表对象获取[0,1,2],但只能从“对象yield
输出”中检索一次。因此,它有一个新名称qazxswpoi对象,如generator
中所示。
总之,作为一个隐喻它的隐喻:
Out[11]: <generator object num_list at 0x10327c990>
和return
是双胞胎yield
和list
是双胞胎下面是一些如何实际实现生成器的Python示例,就像Python没有为它们提供语法糖一样:
作为Python生成器:
generator
使用词法闭包而不是生成器
from itertools import islice
def fib_gen():
a, b = 1, 1
while True:
yield a
a, b = b, a + b
assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))
使用对象闭包而不是生成器(因为def ftake(fnext, last):
return [fnext() for _ in xrange(last)]
def fib_gen2():
#funky scope due to python2.x workaround
#for python 3.x use nonlocal
def _():
_.a, _.b = _.b, _.a + _.b
return _.a
_.a, _.b = 0, 1
return _
assert [1,1,2,3,5] == ftake(fib_gen2(), 5)
)
ClosuresAndObjectsAreEquivalent
我将发布“阅读Beazley的'Python:Essential Reference'第19页,以便快速描述发生器”,但是很多其他人已经发布了很好的描述。
另外,请注意class fib_gen3:
def __init__(self):
self.a, self.b = 1, 1
def __call__(self):
r = self.a
self.a, self.b = self.b, self.a + self.b
return r
assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
可以在协同程序中用作它们在生成器函数中的双重使用。虽然它与您的代码片段的用法不同,但yield
可以用作函数中的表达式。当调用者使用(yield)
方法向方法发送值时,协程将执行,直到遇到下一个send()
语句。
生成器和协同程序是设置数据流类型应用程序的一种很酷的方法。我认为在函数中知道(yield)
语句的其他用法是值得的。
从编程的角度来看,迭代器实现为yield
。
要将并发执行的迭代器,生成器和线程池等实现为thunks(也称为匿名函数),可以使用发送给具有调度程序的闭包对象的消息,并且调度程序将回答“消息”。
“next”是发送到闭包的消息,由“iter”调用创建。
有很多方法可以实现这个计算。我使用了变异,但通过返回当前值和下一个yielder,很容易做到没有变异。
这是一个使用R6RS结构的演示,但语义与Python完全相同。它是相同的计算模型,只需要在Python中重写它就需要改变语法。
http://en.wikipedia.org/wiki/Message_passing
yield
的捷径当你看到一个带有yield
语句的函数时,应用这个简单的技巧来理解会发生什么:
result = []
。yield expr
替换每个result.append(expr)
。return result
。yield
声明!阅读并找出代码。这个技巧可以让你了解函数背后的逻辑,但yield
实际发生的情况与基于列表的方法中发生的情况明显不同。在许多情况下,yield方法将更高效,更快。在其他情况下,即使原始函数工作得很好,这个技巧也会让你陷入无限循环。请继续阅读以了解更多信息...
首先,迭代器协议 - 当你写
for x in mylist:
...loop body...
Python执行以下两个步骤:
mylist
的迭代器:
调用iter(mylist)
- >这将返回一个带有next()
方法的对象(或Python 3中的__next__()
)。
[这是大多数人忘记告诉你的步骤]next()
方法。将next()
的返回值赋给x
并执行循环体。如果从StopIteration
中引发异常next()
,则意味着迭代器中没有更多值,并且退出循环。事实上,Python在任何时候想要循环对象的内容时执行上述两个步骤 - 因此它可能是for循环,但它也可能是像otherlist.extend(mylist)
(其中otherlist
是Python列表)的代码。
这里mylist
是一个可迭代的,因为它实现了迭代器协议。在用户定义的类中,您可以实现__iter__()
方法以使您的类的实例可迭代。此方法应返回迭代器。迭代器是一个使用next()
方法的对象。可以在同一个类上实现__iter__()
和next()
,并让__iter__()
返回self
。这适用于简单的情况,但是当您希望两个迭代器同时循环遍历同一个对象时。
所以这是迭代器协议,许多对象实现了这个协议:
__iter__()
的类。请注意,for
循环不知道它正在处理什么类型的对象 - 它只是遵循迭代器协议,并且很高兴得到项目,因为它调用next()
。内置列表逐个返回它们的项目,字典逐个返回键,文件一个接一个地返回行等。然后生成器返回......那就是yield
的用武之地:
def f123():
yield 1
yield 2
yield 3
for item in f123():
print item
而不是yield
语句,如果你在return
中有三个f123()
语句,只有第一个会被执行,并且函数将退出。但f123()
不是普通的功能。调用f123()
时,它不会返回yield语句中的任何值!它返回一个生成器对象。此外,该功能并没有真正退出 - 它进入暂停状态。当for
循环尝试遍历生成器对象时,函数从之前返回的yield
之后的下一行恢复其挂起状态,执行下一行代码,在本例中为yield
语句,并将其返回为下一个项目。这种情况发生,直到函数退出,此时生成器引发StopIteration
,循环退出。
因此,生成器对象有点像适配器 - 在一端它展示了迭代器协议,通过暴露__iter__()
和next()
方法来保持for
循环快乐。然而,在另一端,它运行该功能足以从中获取下一个值,并将其重新置于挂起模式。
通常,您可以编写不使用生成器但实现相同逻辑的代码。一种选择是使用我之前提到的临时列表'技巧'。这并不适用于所有情况,例如如果你有无限循环,或者当你有一个很长的列表时它可能会低效地使用内存。另一种方法是实现一个新的可迭代类SomethingIter
,它将状态保存在实例成员中,并在其next()
(或Python 3中的__next__()
)方法中执行下一个逻辑步骤。根据逻辑,next()
方法中的代码可能看起来非常复杂并且容易出错。这里的发电机提供了一个简洁的解决方案
这是一个简单的例子:
Welcome to Racket v6.5.0.3.
-> (define gen
(lambda (l)
(define yield
(lambda ()
(if (null? l)
'END
(let ((v (car l)))
(set! l (cdr l))
v))))
(lambda(m)
(case m
('yield (yield))
('init (lambda (data)
(set! l data)
'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
输出:
def isPrimeNumber(n):
print "isPrimeNumber({}) call".format(n)
if n==1:
return False
for x in range(2,n):
if n % x == 0:
return False
return True
def primes (n=1):
while(True):
print "loop step ---------------- {}".format(n)
if isPrimeNumber(n): yield n
n += 1
for n in primes():
if n> 10:break
print "wiriting result {}".format(n)
我不是一个Python开发人员,但它看起来我loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call
持有程序流的位置和下一个循环从“yield”位置开始。似乎它正在等待那个位置,就在此之前,将值返回到外部,然后下一次继续工作。
这似乎是一个有趣而且很好的能力:D
这是yield
所做的精神图像。
我喜欢将一个线程视为具有堆栈(即使它没有以这种方式实现)。
当调用普通函数时,它将其局部变量放在堆栈上,进行一些计算,然后清除堆栈并返回。它的局部变量的值再也看不到了。
使用yield
函数,当它的代码开始运行时(即在调用函数之后,返回一个生成器对象,然后调用其yield
方法),它同样将其局部变量放入堆栈并计算一段时间。但是,当它到达next()
语句时,在清除它的部分堆栈并返回之前,它会获取其局部变量的快照并将它们存储在生成器对象中。它还在其代码中写下它当前所处的位置(即特定的yield
语句)。
所以它是发电机悬挂的一种冻结功能。
当随后调用yield
时,它会将函数的所有物检索到堆栈中并重新设置动画。该功能继续从它停止的位置进行计算,而不知道它刚刚在冷库中度过了永恒的事实。
比较以下示例:
next()
当我们调用第二个函数时,它的行为与第一个函数的行为非常不同。 def normalFunction():
return
if False:
pass
def yielderFunction():
return
if False:
yield 12
语句可能无法访问,但如果它出现在任何地方,它会改变我们正在处理的内容的性质。
yield
调用>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>
不会运行其代码,但会从代码中生成一个生成器。 (为了便于阅读,最好用yielderFunction()
前缀命名这些东西。)
yielder
>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
...
'__iter__', #Returns gen itself, to make it work uniformly with containers
... #when given to a for loop. (Containers return an iterator instead.)
'close',
'gi_code',
'gi_frame',
'gi_running',
'next', #The method that runs the function's body.
'send',
'throw']
和gi_code
字段是存储冻结状态的地方。用gi_frame
探索它们,我们可以证实我们上面的心理模型是可信的。
像每个答案所暗示的那样,dir(..)
用于创建序列生成器。它用于动态生成一些序列。例如,在网络上逐行读取文件时,可以使用yield
函数,如下所示:
yield
您可以在代码中使用它,如下所示:
def getNextLines():
while con.isOpen():
yield con.read()
执行控制转移问题
执行yield时,执行控制将从getNextLines()传送到for line in getNextLines():
doSomeThing(line)
循环。因此,每次调用getNextLines()时,执行都会从上次暂停时开始执行。
因此简而言之,具有以下代码的功能
for
将打印
def simpleYield():
yield "first time"
yield "second time"
yield "third time"
yield "Now some useful value {}".format(12)
for i in simpleYield():
print i
收益率是一个对象
函数中的"first time"
"second time"
"third time"
"Now some useful value 12"
将返回单个值。
如果您希望函数返回一组大量值,请使用return
。
更重要的是,yield
是一个障碍。
就像CUDA语言中的屏障一样,它不会在控制完成之前进行转移。
也就是说,它将从头开始运行函数中的代码,直到它达到yield
。然后,它将返回循环的第一个值。
然后,每隔一个调用将再次运行您在函数中写入的循环,返回下一个值,直到没有任何值返回。
(我的下面的答案仅从使用Python生成器的角度讲,而不是yield
,它涉及堆栈和堆操作的一些技巧。)
当在python函数中使用underlying implementation of generator mechanism而不是yield
时,该函数将变成一种特殊的名为return
的东西。该函数将返回generator function
类型的对象。 generator
关键字是一个标志,用于通知python编译器专门处理此类函数。一旦从其返回某个值,正常函数将终止。但是在编译器的帮助下,生成器函数可以被认为是可恢复的。也就是说,将恢复执行上下文,并且执行将从上次运行继续。直到你显式调用return,这将引发一个yield
异常(它也是迭代器协议的一部分),或者到达函数的末尾。我发现了很多关于StopIteration
的引用,但generator
的one是最可消化的。
(现在我想谈谈functional programming perspective
背后的基本原理,以及基于我自己理解的generator
。我希望这可以帮助你掌握迭代器和生成器的基本动机。这样的概念也出现在其他语言中,比如C#。)
据我了解,当我们想要处理大量数据时,我们通常首先将数据存储在某处,然后逐个处理。但这种天真的方法是有问题的。如果数据量很大,那么事先将它们作为一个整体存储起来是很昂贵的。因此,不是直接存储iterator
本身,为什么不间接存储某种data
,即metadata
。
有两种方法来包装这样的元数据。
the logic how the data is computed
。这就是所谓的as a class
,它实现了迭代器协议(即iterator
和__next__()
方法)。这也是常见的__iter__()
。as a function
。但在引擎盖下,返回的generator function
仍然是generator object
迭代器,因为它还实现了迭代器协议。无论哪种方式,都会创建一个迭代器,即一些可以为您提供所需数据的对象。 OO方法可能有点复杂。无论如何,使用哪一个取决于你。
总之,IS-A
语句将您的函数转换为一个工厂,该工厂生成一个名为yield
的特殊对象,它包裹着原始函数的主体。当generator
被迭代时,它会执行你的函数,直到它到达下一个generator
然后暂停执行并计算传递给yield
的值。它在每次迭代时重复此过程,直到执行路径退出函数。例如,
yield
只是输出
def simple_generator():
yield 'one'
yield 'two'
yield 'three'
for i in simple_generator():
print i
电源来自使用带有计算序列的循环的发生器,发生器每次执行循环停止以“产生”下一个计算结果,这样它就可以动态计算列表,其好处是内存保存用于特别大的计算
假设你想创建一个自己的one
two
three
函数,它产生一个可迭代的数字范围,你可以这样做,
range
并像这样使用它;
def myRangeNaive(i):
n = 0
range = []
while n < i:
range.append(n)
n = n + 1
return range
但这是低效的,因为
幸运的是,Guido和他的团队足够慷慨地开发发电机,所以我们可以做到这一点;
for i in myRangeNaive(10):
print i
现在,每次迭代时,生成器上的一个函数def myRangeSmart(i):
n = 0
while n < i:
yield n
n = n + 1
return
for i in myRangeSmart(10):
print i
执行该函数,直到它达到'yield'语句,在该语句中它停止并“产生”该值或到达函数的末尾。在第一次调用的情况下,next()
执行yield语句并生成'n',在下一次调用时它将执行increment语句,跳回'while',计算它,如果为true,它将停止再次产生'n',它将继续这样,直到while条件返回false并且生成器跳转到函数的末尾。
许多人使用next()
而不是return
,但在某些情况下,yield
可以更高效,更容易使用。
以下是yield
绝对最适合的例子:
返回(在功能中)
yield
产量(功能)
import random
def return_dates():
dates = [] # With 'return' you need to create a list then return it
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
dates.append(date)
return dates
调用函数
def yield_dates():
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
yield date # 'yield' makes a generator automatically which works
# in a similar way. This is much more efficient.
这两个函数都做同样的事情,但dates_list = return_dates()
print(dates_list)
for i in dates_list:
print(i)
dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
print(i)
使用三行而不是五行,并且有一个较少的变量值得担心。
这是代码的结果:
yield
正如你所看到的,两个函数都做同样的事情。唯一的区别是给出了一个列表,return_dates()
给出了一个生成器。
一个现实生活中的例子就像是逐行读取文件或者只是想制作一个生成器。
yield_dates()
就像一个函数的返回元素。区别在于,yield
元素将函数转换为生成器。在某些东西“屈服”之前,生成器的行为就像一个函数。发电机停止,直到下一次调用,并从它开始的完全相同的点继续。通过调用yield
,您可以将所有“已产生”值的序列合二为一。
list(generator())
关键字只是收集返回的结果。想想yield
就像yield
一样
这是一个简单的基于return +=
的方法,用于计算斐波纳契数列,解释如下:
yield
当你将它输入你的REPL然后尝试调用它时,你会得到一个神秘的结果:
def fib(limit=50):
a, b = 0, 1
for i in range(limit):
yield b
a, b = b, a+b
这是因为>>> fib()
<generator object fib at 0x7fa38394e3b8>
的存在向Python发出信号,要求您创建一个生成器,即一个按需生成值的对象。
那么,你如何生成这些值?这可以通过使用内置函数yield
直接完成,也可以通过将其提供给消耗值的构造来间接完成。
使用内置的next
函数,直接调用next()
/ .next
,强制生成器生成一个值:
__next__
间接地,如果你将>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
提供给fib
循环,for
初始化器,list
初始化器或任何其他需要生成/生成值的对象的东西,你将“消耗”生成器,直到它不再生成值(它返回):
tuple
同样,使用results = []
for i in fib(30): # consumes fib
results.append(i)
# can also be accomplished with
results = list(fib(30)) # consumes fib
初始化程序:
tuple
生成器与函数的不同之处在于它是惰性的。它通过维护本地状态并允许您随时恢复来实现此目的。
当你第一次通过调用它调用>>> tuple(fib(5)) # consumes fib
(1, 1, 2, 3, 5)
时:
fib
Python编译函数,遇到f = fib()
关键字并简单地返回一个生成器对象。看起来不是很有帮助。
当你然后请求它直接或间接地生成第一个值时,它会执行它找到的所有语句,直到它遇到yield
,然后它会返回你提供给yield
和暂停的值。举一个更好地证明这一点的例子,让我们使用一些yield
调用(如果在Python 2上用print
替换):
print "text"
现在,输入REPL:
def yielder(value):
""" This is an infinite generator. Only use next on it """
while 1:
print("I'm going to generate the value for you")
print("Then I'll pause for a while")
yield value
print("Let's go through it again.")
你有一个生成器对象现在正在等待命令让它生成一个值。使用>>> gen = yielder("Hello, yield!")
并查看打印内容:
next
不带引号的结果是印刷的。引用的结果是从>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
返回的结果。现在再次致电yield
:
next
发电机记得它在>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
暂停并从那里恢复。打印下一条消息并再次执行搜索yield value
语句以暂停它(由于yield
循环)。
想一想:
对于具有next()
方法的对象,迭代器只是一个奇特的声音术语。因此,屈服函数最终会像这样:
原始版本:
def some_function():
for i in xrange(4):
yield i
for i in some_function():
print i
这基本上是Python解释器对上面代码的处理:
class it:
def __init__(self):
# Start at -1 so that we get 0 when we add 1 below.
self.count = -1
# The __iter__ method will be called once by the 'for' loop.
# The rest of the magic happens on the object returned by this method.
# In this case it is the object itself.
def __iter__(self):
return self
# The next method will be called repeatedly by the 'for' loop
# until it raises StopIteration.
def next(self):
self.count += 1
if self.count < 4:
return self.count
else:
# A StopIteration exception is raised
# to signal that the iterator is done.
# This is caught implicitly by the 'for' loop.
raise StopIteration
def some_func():
return it()
for i in some_func():
print i
为了更深入地了解幕后发生的事情,可以将for
循环重写为:
iterator = some_func()
try:
while 1:
print iterator.next()
except StopIteration:
pass
这更有意义还是让你更加迷惑? :)
我应该注意到,这仅仅是为了说明目的而过于简单化。 :)
一个简单的例子来理解它是什么:while
yield
输出是:
def f123():
for _ in range(4):
yield 1
yield 2
for i in f123():
print i
yield
关键字简化为两个简单的事实:
yield
关键字,则该函数不再通过return
语句返回。相反,它会立即返回一个称为生成器的惰性“挂起列表”对象list
或set
或range
或dict-view,具有以特定顺序访问每个元素的内置协议。简而言之:生成器是一个惰性的,递增挂起的列表,而yield
语句允许您使用函数表示法来编程生成器应逐渐吐出的列表值。
generator = myYieldingFunction(...)
x = list(generator)
generator
v
[x[0], ..., ???]
generator
v
[x[0], x[1], ..., ???]
generator
v
[x[0], x[1], x[2], ..., ???]
StopIteration exception
[x[0], x[1], x[2]] done
list==[x[0], x[1], x[2]]
让我们定义一个函数makeRange
,就像Python的range
一样。致电makeRange(n)
返回发电机:
def makeRange(n):
# return 0,1,2,...,n-1
i = 0
while i < n:
yield i
i += 1
>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>
要强制生成器立即返回其挂起值,您可以将其传递给list()
(就像您可以任意迭代一样):
>>> list(makeRange(5))
[0, 1, 2, 3, 4]
上面的例子可以被认为只是创建一个你追加并返回的列表:
# list-version # # generator-version
def makeRange(n): # def makeRange(n):
"""return [0,1,2,...,n-1]""" #~ """return 0,1,2,...,n-1"""
TO_RETURN = [] #>
i = 0 # i = 0
while i < n: # while i < n:
TO_RETURN += [i] #~ yield i
i += 1 # i += 1 ## indented
return TO_RETURN #>
>>> makeRange(5)
[0, 1, 2, 3, 4]
但是有一个主要的区别;见最后一节。
可迭代是列表推导的最后一部分,并且所有生成器都是可迭代的,因此它们经常被使用:
# _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]
为了更好地了解发电机,您可以使用itertools
模块(确保在保证时使用chain.from_iterable
而不是chain
)。例如,您甚至可以使用生成器来实现像itertools.count()
这样的无限长的惰性列表。您可以实现自己的def enumerate(iterable): zip(count(), iterable)
,或者在while循环中使用yield
关键字实现。
请注意:生成器实际上可以用于更多的东西,例如implementing coroutines或非确定性编程或其他优雅的东西。但是,我在这里提出的“懒惰列表”观点是您会发现的最常见的用途。
这就是“Python迭代协议”的工作原理。也就是说,当你做list(makeRange(5))
时会发生什么。这就是我之前描述的“懒惰的增量列表”。
>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
内置函数next()
只调用对象.next()
函数,它是“迭代协议”的一部分,可以在所有迭代器上找到。您可以手动使用next()
函数(以及迭代协议的其他部分)来实现奇特的东西,通常以牺牲可读性为代价,因此尽量避免这样做......
通常情况下,大多数人不会关心以下区别,可能想在这里停止阅读。
在Python中,可迭代是任何“理解for循环的概念”的对象,如列表[1,2,3]
,迭代器是所请求的for循环的特定实例,如[1,2,3].__iter__()
。生成器与任何迭代器完全相同,除了它的编写方式(使用函数语法)。
从列表中请求迭代器时,它会创建一个新的迭代器。但是,当您从迭代器(您很少这样做)请求迭代器时,它只会为您提供自身的副本。
因此,万一你没有做到这样的事情......
> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]
...然后记住发电机是一个迭代器;也就是说,它是一次性的。如果你想重复使用它,你应该再次调用myRange(...)
。如果需要使用结果两次,请将结果转换为列表并将其存储在变量x = list(myRange(5))
中。那些绝对需要克隆生成器的人(例如,谁正在做可怕的hackish元编程)如果绝对必要的话可以使用itertools.tee
,因为可复制的迭代器Python PEP标准提案已被推迟。
yield
关键字在Python中有什么作用?
yield
函数会返回Generator。yield from
在两个方向上从一个生成器委派给另一个生成器。return
在发电机中的使用。)yield
只在函数定义中是合法的,并且在函数定义中包含yield
使它返回一个生成器。
生成器的想法来自其他语言(见脚注1),具有不同的实现。在Python的Generators中,代码的执行是在yield的点上frozen。当调用生成器时(下面讨论方法),执行重新开始,然后在下一个yield时冻结。
yield
提供了一种简单的implementing the iterator protocol方法,由以下两种方法定义:__iter__
和next
(Python 2)或__next__
(Python 3)。这两个方法都使对象成为迭代器,您可以使用Iterator
模块中的collections
Abstract Base Class进行类型检查。
>>> def func():
... yield 'I am'
... yield 'a generator!'
...
>>> type(func) # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen) # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__') # that's an iterable
True
>>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3)
True # implements the iterator protocol.
生成器类型是迭代器的子类型:
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
如有必要,我们可以像这样打字检查:
>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True
Iterator
is that once exhausted的一个功能,您无法重复使用或重置它:
>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]
如果你想再次使用它的功能,你必须再制作另一个(见脚注2):
>>> list(func())
['I am', 'a generator!']
可以以编程方式生成数据,例如:
def func(an_iterable):
for item in an_iterable:
yield item
上面的简单生成器也等同于下面 - 从Python 3.3开始(在Python 2中不可用),你可以使用yield from
:
def func(an_iterable):
yield from an_iterable
但是,yield from
也允许委托给子发电机,这将在下面关于与协同程序的协同授权的部分中解释。
yield
形成一个表达式,允许将数据发送到生成器(见脚注3)
下面是一个示例,请注意received
变量,它将指向发送到生成器的数据:
def bank_account(deposited, interest_rate):
while True:
calculated_interest = interest_rate * deposited
received = yield calculated_interest
if received:
deposited += received
>>> my_account = bank_account(1000, .05)
首先,我们必须使用内置函数next
排队生成器。它将调用适当的next
或__next__
方法,具体取决于您使用的Python版本:
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0
现在我们可以将数据发送到生成器。 (Sending None
is
the same as calling next
。):
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5
yield from
合作授予Sub-Coroutine现在,回想一下yield from
在Python 3中可用。这允许我们将协同程序委托给子协会:
def money_manager(expected_rate):
under_management = yield # must receive deposited value
while True:
try:
additional_investment = yield expected_rate * under_management
if additional_investment:
under_management += additional_investment
except GeneratorExit:
'''TODO: write function to send unclaimed funds to state'''
finally:
'''TODO: write function to mail tax info to client'''
def investment_account(deposited, manager):
'''very simple model of an investment account that delegates to a manager'''
next(manager) # must queue up manager
manager.send(deposited)
while True:
try:
yield from manager
except GeneratorExit:
return manager.close()
现在我们可以将功能委托给子发生器,它可以像生成器一样使用,如上所述:
>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6
您可以在yield from
中阅读更多关于PEP 380.的精确语义
close
方法在函数执行被冻结时引发GeneratorExit
。这也将由__del__
调用,因此您可以在处理GeneratorExit
的地方放置任何清理代码:
>>> my_account.close()
您还可以抛出一个异常,该异常可以在生成器中处理或传播回用户:
>>> import sys
>>> try:
... raise ValueError
... except:
... my_manager.throw(*sys.exc_info())
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
File "<stdin>", line 2, in <module>
ValueError
我相信我已经涵盖了以下问题的所有方面:
yield
关键字在Python中有什么作用?
事实证明,yield
做了很多。我相信我可以为此添加更全面的例子。如果您想要更多或有一些建设性的批评,请通过下面的评论告诉我。
__iter__
方法。迭代器提供了.next
(Python 2或.__next__
(Python 3)方法,它被for
循环隐式调用,直到它引发StopIteration
,一旦它发生,它将继续这样做。yield
部分。.next
方法,而相反,他应该使用内置函数next
。这将是一个适当的间接层,因为他的代码在Python 3中不起作用。yield
所做的事情无关。yield
提供的方法以及Python 3中的新功能yield from
。顶部/接受的答案是一个非常不完整的答案。yield
在生成器表达或理解。语法当前允许列表理解中的任何表达。
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist
由于屈服是一种表达,因此在一些人的吹捧中,在理解或生成器表达中使用它是有趣的 - 尽管没有引用特别好的用例。
CPython核心开发人员是discussing deprecating its allowance。这是邮件列表中的相关帖子:
2017年1月30日19:05,Brett Cannon写道:
在Sun,2017年1月29日16:39 Craig Rodrigues写道:
无论采用哪种方法我都行。恕我直言,将它们放在Python 3中的方式并不好。
我的投票是它是一个SyntaxError,因为你没有得到你对语法的期望。
我同意这对我们来说是一个明智的地方,因为任何依赖当前行为的代码实在太聪明而无法维护。
在达到目标方面,我们可能希望:
- 3.7中的SyntaxWarning或DeprecationWarning
- 2.7.x中的Py3k警告
- 3.8中的SyntaxError
干杯,尼克。
- Nick Coghlan | ncoghlan at gmail.com |澳大利亚布里斯班
此外,有一个outstanding issue (10544)似乎指向这个从来不是一个好主意的方向(PyPy,一个用Python编写的Python实现,已经提出了语法警告。)
最重要的是,直到CPython的开发人员告诉我们:不要将yield
放在生成器表达式或理解中。
return
语句在Python 2:
在生成器函数中,
return
语句不允许包含expression_list
。在这种情况下,一个简单的return
表明生成器已完成并将导致StopIteration
被提升。
expression_list
基本上是用逗号分隔的任意数量的表达式 - 实际上,在Python 2中,你可以用return
停止生成器,但是你不能返回一个值。
在Python 3:
在生成器函数中,
return
语句指示生成器已完成并将导致StopIteration
被引发。返回值(如果有)用作构造StopIteration
的参数,并成为StopIteration.value
属性。
xrange
对象(Python 3中的range
)不是Iterator
s,即使它们是可迭代的,因为它们可以被重用。像列表一样,它们的__iter__
方法返回迭代器对象。yield
最初是作为声明引入的,这意味着它只能出现在代码块中一行的开头。现在yield
创建了一个yield表达式。 https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt这个变化是proposed,允许用户将数据发送到生成器,就像人们可能收到它一样。要发送数据,必须能够将其分配给某些内容,为此,语句将无效。yield
就像return
- 它返回你告诉它的任何东西(作为一个发生器)。区别在于下次调用生成器时,执行从最后一次调用yield
语句开始。与返回不同,当产生收益时,不会清除堆栈帧,但是控制将被传回给调用者,因此其状态将在下次调用函数时恢复。
对于代码,函数get_child_candidates
的作用类似于迭代器,因此当您扩展列表时,它会一次向新列表添加一个元素。
list.extend
调用迭代器直到它耗尽。对于您发布的代码示例,只返回一个元组并将其附加到列表中将更加清晰。
还有一件事需要提及:一个实际上不必终止收益的函数。我编写了这样的代码:
def fib():
last, cur = 0, 1
while True:
yield cur
last, cur = cur, last + cur
然后我可以在其他代码中使用它,如下所示:
for f in fib():
if some_condition: break
coolfuncs(f);
它确实有助于简化一些问题,并使一些事情更容易使用。
对于那些喜欢最小工作示例的人,请冥想这个交互式Python会话:
>>> def f():
... yield 1
... yield 2
... yield 3
...
>>> g = f()
>>> for i in g:
... print i
...
1
2
3
>>> for i in g:
... print i
...
>>> # Note that this time nothing was printed
TL; DR
def square_list(n):
the_list = [] # Replace
for x in range(n):
y = x * x
the_list.append(y) # these
return the_list # lines
def square_yield(n):
for x in range(n):
y = x * x
yield y # with this one.
每当你发现自己从头开始建立一个列表时,yield
代替每一块。
这是我收益率的第一个“啊哈”时刻。
yield
是一种sugary方式
建立一系列的东西
相同的行为:
>>> for square in square_list(4):
... print(square)
...
0
1
4
9
>>> for square in square_yield(4):
... print(square)
...
0
1
4
9
不同的行为:
收益是单程:你只能迭代一次。当一个函数有一个收益率时,我们称之为generator function。而iterator就是它的回报。这些条款很有启发性。我们失去了容器的便利性,但是获得了根据需要计算的系列的能力,并且任意长。
收益是懒惰的,它推迟了计算。当你调用它时,一个带有yield的函数实际上根本不会执行。它返回一个iterator object,记住它停止的地方。每次在迭代器上调用next()
(这发生在for循环中)执行时,前进到下一个yield。 return
引发StopIteration并结束系列(这是for循环的自然结束)。
产量是多才多艺的。数据不必一起存储,可以一次提供一个。它可以是无限的。
>>> def squares_all_of_them():
... x = 0
... while True:
... yield x * x
... x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
... print(next(squares))
...
0
1
4
9
如果你需要多次传球且系列不太长,只需在上面调用list()
:
>>> list(square_yield(4))
[0, 1, 4, 9]
yield
这个词的精彩选择,因为both meanings适用:
产量 - 生产或提供(如农业)
...提供系列中的下一个数据。
收益 - 让步或放弃(如政治权力)
...放弃CPU执行直到迭代器前进。