“yield”关键字的作用是什么?

python iterator generator yield coroutine 鏡歌 | 2020-02-02 19:35:06


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。


本站提供【Linux疑难问题解决】50元起 ,【爬虫开发】200元起。欢迎咨询QQ:376667689 点击这里给我发消息



30 回答


14138


要了解
yield
的功能,您必须了解什么是生成器。你可以理解生成器,你必须理解iterables。
iterables
当你创建一个列表时,你可以一个一个地阅读它的条目。逐条读取它的项称为迭代:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3

mylist
是一种迭代。当你使用列表理解时,你会创建一个列表,因此一个iterable:,但是你把所有的值都存储在内存中,当你有很多值的时候,这并不总是你想要的。生成器不会将所有值存储在内存中,而是动态生成值:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4

除了您使用
()
而不是
[]
之外,它是相同的。但是,由于生成器只能使用一次,因此您不能每form
for i in mygenerator
第二次:它们计算0,然后for开始计算1,然后逐个结束计算4。
Yield
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!

>>> 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 the 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 the 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 candidate's 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

此代码包含几个智能部分:
循环在列表上迭代,但列表会展开while循环迭代:-)这是一种简洁的方式来遍历所有这些嵌套的数据,即使它有点危险,因为您可以以无限循环结束。在这种情况下,
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
耗尽发电机的所有值,但是
同时,由于没有在同一节点上应用,因此会不断创建新的生成器对象,这些对象将产生不同于以前的值。
方法是一个列表对象方法,它需要一个iterable并将其值添加到列表中。
通常我们将列表传递给它:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

但是在您的代码中,它会得到一个生成器,这很好,因为:
您不需要读取两次值。
您可能有很多子项,并且不希望它们都存储在内存中。
并且它可以工作,因为Python不关心方法的参数是否是列表。Python期望iterable,因此它将使用字符串、列表、元组和生成器!这称为duck类型,也是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())

>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())

>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())

>>> 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,您最好的朋友
Itertools模块包含操作iterable的特殊功能。想复制发电机吗?
链式两台发电机?将嵌套列表中的值分组为一行?
Map / Zip
不创建另一个列表?
那就举个例子?让我们看看四匹马的可能到达顺序:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)

>>> 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)]

了解迭代的内部机制
迭代是一个包含iterable(实现
__iter__()
方法)和迭代器(实现
__next__()
方法)的过程。
iterable是任何对象可以从中获取迭代器。迭代器是允许您在iterable上迭代的对象。
本文将详细介绍for循环如何工作。

2020-02-02 19:35:18
Miku大爱

1920


理解
yield

当您看到一个带有
yield
语句的函数时,使用这个简单的技巧来理解将要发生的事情:
在函数的开头插入一行
result = []

将每个
yield expr
替换为
result.append(expr)

插入一个在函数底部的
return result
行。
Yay-不再有
yield
语句!阅读并找出代码。
将函数与原始定义进行比较。
此技巧可能会让您了解函数背后的逻辑,但实际发生的
yield
与基于列表的方法明显不同。在许多情况下,yield方法的内存效率会更高,速度也更快。在其他情况下,这个技巧会让你陷入无限循环,即使原始函数工作得很好。继续阅读以了解更多…
首先不要混淆Iterables、迭代器和生成器,最多一步人们for可以告诉你关于]
使用迭代器循环项:
继续调用步骤1返回的迭代器上的
next()
方法。
next()
的返回值被分配给
x
并执行循环体。如果一个异常< PRE> >代码> [代码16 ] /代码> 是从内[PRE> >代码> [17 ] < /Calp> 提出的,这意味着迭代器中没有更多的值,并且循环退出。但它也可以是类似于
otherlist.extend(mylist)
的代码(其中
otherlist
是一个Python列表)。
这里
mylist
是一个iterable,因为它实现了迭代器协议。在用户定义的类中,可以实现
__iter__()
方法以使类的实例可访问。此方法应返回迭代器。迭代器是具有
next()
方法的对象。可以在同一个类上同时实现
__iter__()
next()
,并且具有
__iter__()
返回
self
。这在简单的情况下是可行的,但如果你想让两个迭代器同时在同一个对象上循环,就不行了。
这就是迭代器协议,许多对象实现了这个协议:
内置列表、字典、元组、集合,文件。
实现
__iter__()

生成器的用户定义类。
请注意,for循环不知道它处理的是什么类型的对象-它只是遵循迭代器协议,并且很高兴在调用
next()时获得一个又一个项。内置列表逐个返回项,字典逐个返回键,文件逐个返回行,等等,生成器返回。。。那么,

>代码> [代码] > /PREP>进来:
< PRE> > /代码> < /PRE> > BR>而不是< Prime>代码> [38 ] < /代码> 语句,如果您在代码> [34 ] < /Pr>中有三个

 >代码>返回/代码> 
语句,只有第一个将被执行,函数将退出。但是
f123()
不是普通函数。当调用
f123()
时,它不会返回yield语句中的任何值!它返回一个生成器对象。此外,函数并没有真正退出-它进入一个暂停状态。当
for
循环尝试在生成器对象上循环时,函数将在先前返回的
yield
之后的下一行从其挂起状态恢复,执行下一行代码,在这种情况下,返回一个
yield
语句,并将其作为下一项返回。这发生在函数退出之前,在这一点,生成器提出了
  [40 ] < /代码> /PRE>,并且循环退出。
因此,生成器对象有点像一个适配器——在一端,它展示了迭代器协议,通过公开
__iter__()
next()
方法来保持
的循环愉快。但是,在另一端,它运行的函数刚好可以从中获取下一个值,并将其放回挂起模式。
为什么要使用生成器?
通常可以编写不使用生成器但实现相同逻辑的代码。一种选择是使用我之前提到的临时列表“技巧”。这在所有情况下都不起作用,例如,如果您有无限循环,或者当您有一个非常长的列表时,它可能会使内存的使用效率低下。另一种方法是实现一个新的iterable类
SomethingIter
,它将状态保存在实例成员中,并在Python 3的
next()
(或
__next__()
方法中执行下一个逻辑步骤。根据逻辑的不同,方法中的代码可能看起来非常复杂并且容易出错。这里的发电机提供了一个干净和简单的解决方案。

2020-02-02 19:35:18
久远之灯火

524


这样想:
迭代器只是一个听起来很花哨的术语for一个对象,它有一个
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

这是更有意义还是只是让您更加困惑?:)
我应该注意,出于说明的目的,这是一个过于简单的说明。:)

2020-02-02 19:35:18
囧橙

437


yield
关键字被简化为两个简单的事实:
如果编译器在函数中的任何位置检测到
yield
关键字,则该函数不再通过
return
语句returns。相反,它会立即返回一个懒惰的“pendinglist”对象,名为generator
generator是iterable的。什么是iterable?它类似于一个
列表或
set
range
或dict视图,具有一个内置的协议,用于按特定顺序访问每个元素。
简而言之:生成器是一个惰性的、增量挂起的列表,并且
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)


若要强制生成器立即返回其挂起的值,可以将其传递到
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是列表理解的最后一部分,所有生成器都是iterable,因此,它们通常是这样使用的:
#                   _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
关键字来实现。
请注意:生成器实际上可以用于更多的事情,例如实现协同路由、非确定性编程或其他优雅的事情。然而,我在这里提出的“惰性列表”观点是您将发现的最常用的用法。
幕后
这就是“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 "", line 1, in
StopIteration

内置函数
next()
只调用对象
.next()
函数,它是“迭代协议”的一部分,可以在所有迭代器上找到。您可以手动使用
next()
函数(以及迭代协议的其他部分)来实现一些奇特的东西,通常是以牺牲可读性为代价的,因此请尽量避免这样做……
通常,大多数人都不关心下面的区别,可能希望在这里停止阅读,iterable是“理解for循环概念”的任何对象,如list
[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))
中。如果绝对有必要,那些绝对需要克隆生成器的人(例如,那些正在进行骇人听闻的元编程的人)可以使用
itertools.tee
,因为可复制的迭代器Python PEP标准提案已经被推迟了。

2020-02-02 19:35:18
艾尔就是艾尔

355


在Python中,
yield
关键字的作用是什么?
回答Outline/Summary
调用带有
yield
的函数时,return是一个生成器。
生成器是迭代器,因为它们实现迭代器协议,所以您可以对它们进行迭代器。
生成器也可以发送for信息,使其在概念上成为一个协程。
在Python 3中,您可以使用
yield from

从一个生成器向另一个生成器进行双向委托(附录评论了几个答案,包括最上面的答案,并讨论了在生成器中使用
return

生成器:
yield
仅在函数定义中合法,并且包含函数定义中的
yield
使其return成为生成器。
生成器的思想来自其他语言(见脚注1),具有不同的实现方式。在Python的生成器中,代码的执行在yield处被冻结。当调用生成器(下面讨论方法)时,执行将恢复,然后冻结在nextyield。
yield
提供了一种实现迭代器协议的简单方法,由以下两种方法定义:
__iter__
next
(Python 2)或
__next__
(Python 3)。这两种方法都使对象成为迭代器,您可以使用
Iterator
抽象基
类从
collections
模块中进行类型检查。
>>> def func():
... yield 'I am'
... yield 'a generator!'
...
>>> type(func) # A function with yield is still a function

>>> gen = func()
>>> type(gen) # but it returns a 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
的特性是一旦用完,您不能重复使用或重置它:
>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

如果您想再次使用它的功能,就必须创建另一个(请参见脚注2):
>>> list(func())
['I am', 'a generator!']

可以编程方式yield数据,例如:
def func(an_iterable):
for item in an_iterable:
yield item

上述简单生成器也相当于下面的生成器-从Python 3.3开始(在Python2中不可用),您可以使用
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

,现在我们可以将数据发送到生成器。(发送
None
与调用
next
相同):
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

使用
yield from

现在,请记住,Python 3中提供了
yield from
的子协同委托。这允许我们将协同路由委托给一个子路由:
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

您可以阅读更多关于PEP 380中
yield from
的精确语义的信息。
其他方法: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 "", line 4, in
File "", line 2, in
ValueError

结论
我相信我已经涵盖了以下问题的所有方面:
在Python中,
yield
关键字做什么?
结果发现
yield
做了很多事情。我相信我可以在这上面再加上更多的例子。如果你想要更多的或者有一些建设性的批评,请让我在下面评论一下。
附录:
对最重要/被接受的答案的评论**
它混淆了什么是可以接受的,只是用一个列表作为例子。请参阅上面的参考资料,但总而言之:iterable有一个使用迭代器的
__iter__
方法return。迭代器提供了一个
.next
(Python 2或
.__next__
(Python 3)方法,该方法由
隐式调用,用于循环直到它引发
StopIteration
,一旦它这样做了,它将继续这样做。
然后使用生成器表达式来描述生成器是什么。由于生成器只是创建迭代器的一种简便方法,它只会混淆问题,而且我们还没有到达
yield
部分。
在控制生成器耗尽时,他调用
.next
方法,而应该使用内置函数
next
。这将是一个适当的间接层,因为他的代码在Python3中不工作?这与Python 3中
yield
所做的根本没有关系。
没有讨论
yield
提供的方法以及新功能
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核心开发人员正在讨论取消它的津贴。
以下是来自邮件列表的相关帖子:
2017年1月30日19:05,Brett Cannon在Sun上写道:
,2017年1月29日16:39,克雷格·罗德里格斯写道:
我对这两种方法都没意见。把事情留在Python 3中是不好的,IMHO.
我的投票是它是一个语法错误,因为你没有从语法中得到你所期望的。
我同意我们可以在这里结束,任何依赖当前行为的代码都太聪明了,无法维护。
就实现这一点而言,我们可能需要:
3.7中的SyntaxWarning或DeprecationWarning
2.7.x中的Py3k warning
3.8中的SyntaxError
干杯,尼克。
--gmail.com上的Nick Coghlan | ncoghlan一个悬而未决的问题(10544)似乎指向了这个从来都不是好主意的方向(PyPy,一个用Python编写的Python实现,已经引发了语法警告),除非CPython的开发人员告诉我们:不要将
yield
放在生成器表达式或理解中。
在Python 2中,生成器中的
return
语句:
在生成器函数中,不允许
return
语句包含
expression_list
。在这种情况下,一个裸露的
return
表示生成器已经完成并将导致
StopIteration
被提升。
一个
expression_list
基本上是由逗号分隔的任意数量的表达式-本质上,在Python 2中,您可以使用
return
来停止生成器,但是您不能返回一个值。
在Python 3中:
在生成器函数中,
return
语句指示生成器已完成并将导致引发
StopIteration
。返回的值(如果有的话)用作构造
StopIteration
的参数,并成为
StopIteration.value
属性。
脚注
建议中引用了CLU、Sather和Icon语言
以将生成器的概念引入Python。一般的想法是
一个函数可以保持内部状态,并根据用户的需求生成中间
数据点。这承诺在性能上优于其他方法,包括Python线程,它甚至在某些系统上都不可用。
这意味着,例如,对象(
<70]
在Python 3中为
<71]
)不是
<72]
,尽管它们是可访问的,因为它们可以重复使用。与列表一样,它们的
__iter__
方法返回迭代器对象。

yield
最初是作为语句引入的,这意味着它只能出现在代码块中一行的开头。
现在
yield
创建一个yield表达式。
https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt
此更改旨在允许用户将数据发送到生成器,就像接收数据一样。要发送数据,必须能够将其分配给某个对象,而
为此,语句将不起作用。

2020-02-02 19:35:18
wangswz

309


yield
就像
return
-它返回您告诉它的任何内容(作为生成器)。不同之处在于,下次调用生成器时,执行从上次调用
yield
语句开始。与return不同的是,当发生yield时堆栈帧不会被清理,但是控件会被传输回调用方,因此下次调用函数时它的状态将恢复。
对于代码,函数
get_child_candidates
的作用类似于迭代器,因此当您扩展列表时,它一次添加一个元素到新的列表。
list.extend
调用迭代器,直到它耗尽为止。对于您发布的代码示例,返回一个元组并将其附加到列表中会更清楚。

2020-02-02 19:35:18
rmbhaha

230


还有一件事要提:生成的函数实际上不必终止。我已经编写了这样的代码:
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);

它确实有助于简化一些问题,并使一些事情更易于处理。

2020-02-02 19:35:18
总插不进去

211


对于那些喜欢一个最小的工作示例的人,请考虑这个交互式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

2020-02-02 19:35:18
已苍SAMA

195


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的第一个“aha”时刻。
yield
是一个甜言蜜语的说法,可以说构建一系列的东西
相同的行为:
>>> for square in square_list(4):
... print(square)
...
0
1
4
9
>>> for square in square_yield(4):
... print(square)
...
0
1
4
9

不同的行为:
收益率是一次通过:只能重复一次。当一个函数有屈服点时,我们称它为生成器函数。迭代器就是它的本质,这些术语揭示了问题的本质。我们失去了容器的便利性,却获得了按需计算的任意长的级数的幂。
屈服是懒惰的,它推迟了计算。当你调用一个函数时,它里面有一个屈服点的函数实际上根本不执行。它返回一个迭代器对象,该迭代器对象会记住它离开的地方。每次在迭代器上调用
next()
时(这在for循环中发生),执行距离下一个结果只有几英寸。
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
,因为这两种含义都适用:
yield-production or provide(如在农业中)
序列中的下一个数据。
放弃或放弃(如在政治权力中)
…在迭代器前进之前放弃CPU执行。

2020-02-02 19:35:18
蓝白色灵魂

180


屈服给你一个发电机。
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

bar.next()
1
bar.next()
3
bar.next()
5

如您所见,在第一种情况下,
foo
会将整个列表同时保存在内存中。有5个元素的列表没什么大不了的,但是如果你想要一个500万的列表呢?这不仅是一个巨大的内存消耗器,而且在调用函数时还需要花费大量的时间来构建。
在第二种情况下,
bar
只是给了您一个生成器。生成器是一个iterable——这意味着您可以在for循环中使用它,但每个值只能访问一次。所有的值也不会同时存储在内存中;生成器对象“记住”上一次调用它时它在循环中的位置——这样,如果使用iterable to(比方说)count to 500亿,就不必一次计算到500亿,也不必存储500亿来计数。
同样,这是一个相当不错的假想的例子,如果你真的想数到500亿,你可能会使用itertools。:)
这是生成器最简单的用例。正如您所说的,它可以用来编写有效的排列,使用yield通过调用堆栈向上推,而不是使用某种堆栈变量。生成器还可用于特殊的树遍历和所有其他操作。

2020-02-02 19:35:18
キョン

173


它正在返回一个发电机。我对Python不是特别熟悉,但如果你熟悉的话,我相信它和C的迭代器块是一样的。
关键的想法是编译器/解释器/whatever做了一些诡计,所以就调用者而言,他们可以继续调用next(),它将继续返回值,就像生成器方法被暂停一样。现在很明显你不能真正“暂停”一个方法,所以编译器会建立一个状态机,让你记住你现在在哪里,本地变量是什么样子。这比自己编写迭代器容易得多。

2020-02-02 19:35:18
cfxiao

158


在描述如何使用生成器的众多优秀答案中,有一种我觉得还没有给出。下面是编程语言理论的答案:
Python中的
yield
语句返回生成器。Python中的生成器是一个返回continuations的函数(特别是一种协同程序,但continuations表示更一般的机制来理解正在发生的事情)。
编程语言理论中的continuations是一种更基本的计算,但它们并不经常使用,因为它们非常困难要讲道理也很难落实。但是,关于什么是延拓的概念是直截了当的:它是一个尚未完成的计算状态。在此状态下,保存变量的当前值、尚未执行的操作等。然后在程序中的某个时刻,可以调用continuation,这样程序的变量被重置为该状态并执行保存的操作。
在这种更一般的形式中,continuation可以通过两种方式实现。以
call/cc
方式,程序的堆栈被逐字保存,然后当调用continuation时,堆栈被还原。
在continuation passing style(CPS)中,延续只是普通函数(仅在函数是第一类的语言中),程序员显式地管理这些函数并将其传递给子例程。在这种样式中,程序状态由闭包(以及碰巧在闭包中编码的变量)表示,而不是由位于堆栈某处的变量表示。管理控制流的函数接受continuation作为参数(在某些CPS变体中,函数可能接受多个continuation),并通过调用控制流并随后返回来操作控制流。延续传递样式的一个非常简单的示例如下:
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)

在这个(非常简单的)示例中,程序员保存将文件实际写入延续的操作(这可能是一个非常复杂的操作,需要写出许多细节),然后传递该延续(即,作为一类闭包)发送给另一个执行更多处理的运算符,然后在必要时调用它。(我在实际的GUI编程中经常使用这种设计模式,这是因为它节省了代码行,或者更重要的是,它可以在GUI事件触发后管理控制流。)
本文的其余部分将在不丧失一般性的情况下,将延续概念化为CPS,因为它非常容易理解和阅读。
现在让我们来谈谈Python中的生成器。生成器是continuation的特定子类型。尽管continuations通常能够保存计算的状态(即程序的调用堆栈),但生成器只能保存迭代器上的迭代状态。尽管如此,这个定义对于生成器的某些用例来说有点误导。例如:
def f():
while True:
yield 4

这显然是一个合理的iterable,它的行为定义得很好——每次生成器遍历它,它都返回4(并且永远这样做)。但在考虑迭代器时,它可能并不是想到的iterable的典型类型(即
for x in collection: do_something(x)
)。这个例子说明了生成器的能力:如果任何东西是迭代器,生成器可以保存其迭代的状态。
重申:Continuations可以保存程序堆栈的状态,生成器可以保存迭代的状态。这意味着延续比生成器强大得多,但生成器也简单得多。它们对于语言设计者来说更容易实现,对于程序员来说也更容易使用(如果你有时间,试着阅读并理解这个关于continuations和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
关键字。

2020-02-02 19:35:18
囧葱歌

141


这里有一个简单语言的例子。我将提供高级人类概念与低级Python概念之间的对应关系。
我想对sequence个数字进行操作,但我不想在创建序列时打扰自己,我只想专注于我想做的操作。所以,我做如下的工作:
我打电话给你,告诉你我想要一个以特定方式产生的数字序列,我让你知道算法是什么。
此步骤对应于生成函数中的
def
即包含
yield

的函数,稍后,我告诉您,“好,准备好告诉我数字的顺序”。
此步骤对应于调用生成器函数,该函数return是生成器对象。注意,你还没有告诉我任何数字,你只要拿起纸和铅笔就行了。
我问你,“告诉我下一个数字”,你告诉我第一个数字;然后,你等着我问你下一个数字。你的工作是记住你在哪里,你已经说过什么数字,下一个数字是什么。我不在乎细节。
此步骤对应于在生成器对象上调用
.next()

…重复上一步,直到…
最终,您可能会结束。你没有告诉我一个数字,你只是大声喊道:“别急!我完了!再也没有号码了!”
此步骤对应于生成器对象结束其作业并引发异常生成器函数不需要引发异常。当函数结束或发出
return

这是生成器所做的(包含
yield
的函数);它开始执行,当它执行
yield
时暂停,当要求
.next()
值时,它从它原来的点继续最后。它完全符合Python的迭代器协议(iterator protocol),该协议描述了如何顺序请求值。
迭代器协议最著名的用户是Python中的
for
命令。因此,每当您执行以下操作时:
for item in sequence:

无论
序列是列表、字符串、字典还是如上所述的生成器对象;结果是相同的:您逐个读取序列中的项。
请注意,在包含
yield
关键字的函数中
def
不是创建生成器的唯一方法;它只是创建生成器的最简单方法。
有关更准确的信息,请阅读有关迭代器类型的信息,Python文档中的yield语句和生成器。

2020-02-02 19:35:18
八云紫_

124


虽然很多答案都说明了为什么要使用
yield
来创建生成器,但是
yield
还有更多的用途。做一个协程是很容易的,它允许在两个代码块之间传递信息。我不会重复已经给出的使用
yield
创建生成器的任何好例子。
要帮助理解
yield
在下面的代码中的作用,可以使用手指在任何具有
yield
的代码中跟踪循环。每次手指碰到
yield
时,您都必须等待输入
next
send
。当调用
next
时,跟踪代码,直到点击
yield
yield
右侧的代码将被计算并返回给调用方…然后等待。当再次调用
next
时,您将在代码中执行另一个循环。但是,您会注意到,在协同过程中,
yield
还可以与
send
一起使用,它将send一个值从调用者输入yielding函数。如果给定了发送,则接收发送的值,并从左侧将其吐出…然后继续跟踪代码,直到您再次点击
为止(返回末尾的值,好像调用了
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()

2020-02-02 19:35:18
陆路拉拉

119


还有一个用法和意义(自Python 3.3以来):
yield from 

来自PEP 380——委托给子生成器的语法:
建议生成器将其部分操作委托给另一个生成器的语法。这允许将包含'yield'的一段代码分解并放入另一个生成器中。另外,允许子生成器返回一个值,并且该值可供委托生成器使用。
当一个生成器重新生成另一个生成器生成的值时,新语法还为优化提供了一些机会。
此外,这将引入(因为Python 3.5):
async def new_coroutine(data):
...
await blocking_action()

以避免将协程与常规生成器混淆(今天
yield
用于这两种情况。

2020-02-02 19:35:18
蒼白

104


所有好的答案,对新手来说都有点难。
我想你已经学会了
return
语句。
作为类比,
return
yield
是双胞胎。
return
表示“return和stop”,而“yield`表示“[7”,但请继续“

尝试使用
return

def num_list(n):
for i in range(n):
return i

运行它:
In [5]: num_list(3)
Out[5]: 0

请参阅,您只得到一个数字,而不是一个列表。pre>哪个运行一旦停止,
yield
运行您计划的时间。
您可以将
return
解释为
return one of them
,将
yield
解释为
return all of them
。这称为
iterable


我们还可以用
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
语句这是关于
yield

列表返回与对象输出之间的区别
yield
输出是:
您将始终从列表对象中获取[0,1,2],但只能从“对象
yield
输出”中检索一次。因此,它有了一个新的名称
generator
object,如
Out[11]: 

中所示,总之,作为对它的一个暗喻:
return
yield
是双胞胎
list
generator
是双胞胎

2020-02-02 19:35:18
bbby

99

这里有一些Python实例,说明Python实际上是如何为Python提供语法糖的:BR>作为Python生成器:

  [代码> < /PRE> > BR>使用词法闭包代替生成器
< PRE> >代码> [ 1 ] < /PRE> > BR>使用对象闭包而不是生成器(因为)关闭和对象等效)
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)

2020-02-02 19:35:18
八雲と靈夢

94


从编程的角度来看,迭代器被实现为thunks。
要实现迭代器、生成器和线程池以进行并发执行,等等。作为thunks(也称为匿名函数),使用发送到闭包对象的消息,闭包对象有一个分派器,调度器回答“消息”。
http://en.wikipedia.org/wiki/Message_passing
“next”是发送到闭包的消息,由“iter”调用创建。
有很多方法可以实现此计算。我使用了变异,但是不需要变异就很容易做到,返回当前值和下一个yielder。
这里有一个使用R6RS结构的演示,但是语义与Python完全相同。它是相同的计算模型,在Python中重写它只需要更改语法。
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
->

2020-02-02 19:35:18
帝國暴風兵

93


我本打算发布“阅读Beazley's‘Python:Essential Reference’的第19页以快速描述生成器”,但其他许多人已经发布了很好的描述。
另外,请注意,在协同程序中可以使用
yield
作为它们在生成器函数中的双重用途。尽管它与代码片段的用途不同,但可以将其用作函数中的表达式。当调用方使用
send()
方法向方法发送值时,协程将执行,直到遇到下一个
(yield)
语句为止。
生成器和协程是设置数据流类型应用程序的一种很酷的方法。我认为了解函数中
yield
语句的其他用法是值得的。

2020-02-02 19:35:18
何苦

81


这里有一个简单的例子:
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)

输出:
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

我不是Python开发人员,但在我看来,它保持程序流的位置,下一个循环从“yield”位置开始。似乎它就在那个位置等待,就在那个位置之前,返回一个外部值,下一次继续工作。
这似乎是一个有趣而美好的能力:D

2020-02-02 19:35:18
mayona13

66


这里是一个关于
yield
所做事情的大脑图像。
我喜欢把线程看作有一个堆栈(即使它不是这样实现的)。
当调用普通函数时,它将其局部变量放在堆栈上,进行一些计算,然后清除堆栈并返回。它的局部变量的值再也看不到了。
使用
yield
函数,当它的代码开始运行时(即在调用函数后,返回一个生成器对象,然后调用其
next()
方法),它类似地将它的局部变量放在堆栈上并计算一段时间。但是,当它命中
yield
语句时,在清除堆栈的一部分并返回之前,它会获取本地变量的快照并将它们存储在生成器对象中。它还将当前所在的位置写在代码中(即特定的
yield
语句)。
因此,它是生成器挂在上面的一种冻结函数。
当随后调用
next()
时,它将函数的属性检索到堆栈上并重新设置动画。这个函数继续从它停止的地方开始计算,忽略了它刚刚在冷库中度过了一段永恒的时间这一事实。
比较以下示例:
def normalFunction():
return
if False:
pass
def yielderFunction():
return
if False:
yield 12

当我们调用第二个函数时,它的行为与第一个函数的行为非常不同。
yield
语句可能无法访问,但如果它出现在任何地方,它会改变我们处理的内容的性质。
>>> yielderFunction()


调用
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(..)
来探索它们,我们可以确认上面的心理模型是可信的。

2020-02-02 19:35:18
血妖骑

56


正如每个答案所暗示的,使用
yield
创建序列生成器。它用于动态生成一些序列。例如,在网络上逐行读取文件时,您可以使用
yield
函数,如下所示:
def getNextLines():
while con.isOpen():
yield con.read()

您可以在代码中使用它,如下所示:
for line in getNextLines():
doSomeThing(line)

执行控制传输gotcha
当执行yield时,执行控制将从getNextLines()传输到
for
循环。因此,每次调用getNextLines()时,执行从上次暂停的点开始。
因此,简而言之,具有以下代码的函数将打印
def simpleYield():
yield "first time"
yield "second time"
yield "third time"
yield "Now some useful value {}".format(12)
for i in simpleYield():
print i

2020-02-02 19:35:18
银发灼眼

51


(我下面的回答只是从使用Pythongenerator的角度讲的,而不是generator机制的底层实现,它涉及堆栈和堆操作的一些技巧。)
当在Python函数中使用
yield
而不是
return
时,这个函数被转换成一个特殊的函数,称为
generator function
。该函数将返回
generator
类型的对象。
yield
关键字是一个标志,用于通知python编译器对此类函数进行特殊处理。正常函数将在返回某个值后终止。但是在编译器的帮助下,generator function可以被认为是可恢复的。也就是说,将还原执行上下文,并从上次运行开始继续执行。除非显式调用return,否则将引发异常(也是iterator协议的一部分),或者到达函数的末尾。我找到了很多关于
生成器的参考资料,但是这个来自
functional programming perspective
的是最容易理解的。
(现在我想谈谈
生成器背后的原理,以及基于我自己的理解的
iterator
。我希望这能帮助您理解迭代器和生成器的基本动机。这一概念也出现在其他语言中,如C#.
据我所知,当我们想处理一堆data数据时,通常先将数据存储在某个地方,然后逐个处理。但这种天真的做法是有问题的。如果数据量很大,那么事先将它们作为一个整体存储是很昂贵的。因此,与其直接存储
数据本身,不如间接存储某种
metadata
,即
the logic how the data is computed

有两种方法可以包装这种元数据。
在OO方法中,我们包装元数据。这就是所谓的实现迭代器协议的迭代器(即
__next__()
__iter__()
方法)。这也是常见的迭代器设计模式。
在函数方法中,我们包装元数据。这就是所谓的发电机功能。但在引擎盖下,返回的
generator object
迭代器仍然
IS-A
迭代器,因为它还实现迭代器协议。
无论哪种方式,都会创建迭代器,即可以为您提供所需数据的某个对象。面向对象方法可能有点复杂。不管怎样,使用哪一个取决于您。

2020-02-02 19:35:18
誇誇

50


Yield是一个对象,函数中的A
return
将返回一个值。
如果希望函数返回大量值,请使用
yield

更重要的是,
yield
是一个屏障。
就像CUDA语言中的屏障一样,在完成之前,它不会传输控制。
也就是说,它将从函数的开始运行代码,直到点击
yield
。然后,它将返回循环的第一个值。
然后,每个其他调用将再次运行您在函数中编写的循环,返回下一个值,直到没有任何值可返回。

2020-02-02 19:35:18
暮光教徒

50


总之,
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

并像这样使用它;
for i in myRangeNaive(10):
print i

但这是低效的,因为
您创建的数组只使用一次(这会浪费内存)
这段代码实际上在数组上循环两次!:(
幸运的是,Guido和他的团队慷慨地开发了发电机,因此我们可以这样做;
def myRangeSmart(i):
n = 0
while n < i:
yield n
n = n + 1
return
for i in myRangeSmart(10):
print i

现在,在每次迭代之后,生成器上名为
next()的函数将执行该函数,直到它到达“yield”语句,在该语句中停止并“产生”值或到达函数的结尾。在这种情况下,在第一次调用时,
next()
执行到yield语句并产生'n',在下一次调用时,它将执行increment语句,跳回到'while',计算它,如果为true,它将停止并再次产生'n',它将继续这样,直到while条件返回false,生成器跳到函数的结束。

2020-02-02 19:35:18
韩光雪

49


许多人使用
return
而不是
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

yield(在函数中)
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()
给出了一个生成器。
一个真实的例子就像逐行读取一个文件,或者你只想生成一个生成器。

2020-02-02 19:35:18
熊猫ZETA

48


一个容易理解的例子是:
yield

def f123():
for _ in range(4):
yield 1
yield 2

for i in f123():
print i

输出是:
1 2 1 2 1 2 1 2

2020-02-02 19:35:18
godspeedpj

40


yield
就像函数的返回元素。区别在于,
yield
元素将函数转换为生成器。生成器的行为就像一个函数,直到“产生”某个东西。生成器在下一次调用之前停止,并从与它启动时完全相同的点继续。通过调用
list(generator())

2020-02-02 19:35:18
宇春真汉子

38


yield
关键字只收集返回的结果。想想
yield
就像
return +=

2020-02-02 19:35:18
半夜睡觉

35


这里有一个简单的基于
yield
的方法来计算fibonacci级数,解释如下:
def fib(limit=50):
a, b = 0, 1
for i in range(limit):
yield b
a, b = b, a+b

当您将它输入到REPL并尝试调用它时,您将得到一个令人迷惑的结果:
>>> fib()


这是因为
yield
的存在向Python发出了要创建生成器的信号,也就是说,一个按需生成值的对象。
那么,如何生成这些值?这可以直接通过使用内置函数
next
来完成,也可以通过将其提供给使用值的构造来间接完成。
使用内置的
next()
函数,您可以直接调用
.next
/
__next__
,for对生成器进行循环以产生值:
>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

间接地,如果您为循环提供
fib
的值,a
list
初始值设定项、a
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时:
f = fib()

Python编译函数,遇到
yield
关键字并简单地返回生成器对象。似乎没什么帮助。
然后,当您请求它直接或间接地生成第一个值时,它将执行它找到的所有语句,直到遇到
yield
,然后yield返回您提供给
yield
的值并暂停。为了更好地演示这一点,让我们使用一些
print
调用(如果在Python 2上,则替换为
print "text"

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.")

现在,在REPL中输入:REPL:
>>> 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
语句的搜索(由于
while
循环)

2020-02-02 19:35:18
小五萝莉



Copyright © 2019-2020 zimt8.com
备案号:湘ICP备19012068号