Python中yield关键字的用途是什么? 它有什么作用?

例如,我试图理解这段代码1

def _get_child_candidates(self, distance, min_dist, max_dist):if self._leftchild and distance - max_dist < self._median:yield self._leftchildif 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方法时会发生什么? 是否返回列表? 一个元素? 再叫一次吗? 后续通话何时停止?


1.这段代码是由Jochen Schulz(jrschulz)编写的,Jochen Schulz是一个很好的用于度量空间的Python库。这是完整源代码的链接: Module mspace 。


#1楼

以下是一些Python示例,这些示例说明如何实际实现生成器,就像Python没有为其提供语法糖一样:

作为Python生成器:

from itertools import islicedef fib_gen():a, b = 1, 1while True:yield aa, b = b, a + bassert [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 nonlocaldef _():_.a, _.b = _.b, _.a + _.breturn _.a_.a, _.b = 0, 1return _assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

使用对象闭包而不是生成器 (因为ClosuresAndObjectsAreEquivalent )

class fib_gen3:def __init__(self):self.a, self.b = 1, 1def __call__(self):r = self.aself.a, self.b = self.b, self.a + self.breturn rassert [1,1,2,3,5] == ftake(fib_gen3(), 5)

#2楼

产量可以为您提供发电机。

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可以将内容推入调用堆栈,而不是使用某种堆栈变量。 生成器还可以用于特殊的树遍历以及所有其他方式。


#3楼

对于那些偏爱简单工作示例的人,请在此交互式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

#4楼

我本打算发布“阅读Beazley的“ Python:基本参考”的第19页,以快速了解生成器”,但是已经有许多其他人发布了不错的描述。

另外,请注意,协程可以将yield用作生成函数的双重用途。 尽管(yield)与代码段用法不同,但它可以用作函数中的表达式。 当调用者使用send()方法向该方法发送值时,协程将一直执行,直到遇到下一个(yield)语句为止。

生成器和协程是设置数据流类型应用程序的一种很酷的方法。 我认为值得了解函数中yield语句的其他用法。


#5楼

在描述如何使用生成器的许多很棒的答案中,我还没有给出一种答案。 这是编程语言理论的答案:

Python中的yield语句返回一个生成器。 (and specifically a type of coroutine, but continuations represent the more general mechanism to understand what is going on). Python中的生成器是一个返回的函数(特别是协程类型,但是延续代表了一种更通用的机制来了解正在发生的事情)。

编程语言理论中的连续性是一种更为基础的计算,但是由于它们很难推理而且也很难实现,因此并不经常使用。 但是,关于延续是什么的想法很简单:只是尚未完成的计算状态。 在此状态下,将保存变量的当前值,尚未执行的操作等。 然后,在稍后的某个时刻,可以在程序中调用继续,以便将程序的变量重置为该状态,并执行保存的操作。

以这种更一般的形式进行的延续可以两种方式实现。 以call/cc方式,该程序的堆栈实际上是保存的,然后在调用延续时,该堆栈得以恢复。

在延续传递样式(CPS)中,延续只是普通的函数(仅在函数为第一类的语言中),程序员明确地对其进行管理并传递给子例程。 以这种方式,程序状态由闭包(以及恰好在其中编码的变量)表示,而不是驻留在堆栈中某个位置的变量。 管理控制流的函数接受连续作为参数(在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)

在这个(非常简单的)示例中,程序员保存了将文件实际写入连续的操作(该操作可能是非常复杂的操作,需要写出许多细节),然后传递该连续(例如,首先类关闭)到另一个进行更多处理的运算符,然后在必要时调用它。 (我在实际的GUI编程中经常使用这种设计模式,这是因为它节省了我的代码行,或更重要的是,在GUI事件触发后管理了控制流。)

在不失一般性的前提下,本文的其余部分将连续性概念化为CPS,因为它很容易理解和阅读。

现在让我们谈谈Python中的生成器。 生成器是延续的特定子类型。 延续通常能够保存计算状态 (即程序的调用堆栈),而生成器只能保存迭代上的迭代状态 。 虽然,对于发电机的某些用例,此定义有些误导。 例如:

def f():while True:yield 4

显然,这是一个合理的迭代器,其行为已得到很好的定义-每次生成器对其进行迭代时,它都会返回4(并永远这样做)。 但是在考虑迭代器时(例如, for x in collection: do_something(x) ),可能不会想到原型的可迭代类型。 此示例说明了生成器的功能:如果有什么是迭代器,生成器可以保存其迭代状态。

重申一下:连续可以保存程序堆栈的状态,而生成器可以保存迭代的状态。 这意味着延续比生成器强大得多,但是生成器也非常简单。 它们对于语言设计者来说更容易实现,对程序员来说也更容易使用(如果您有时间要燃烧,请尝试阅读并理解有关延续和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_continuationreturn value

其中yield关键字实际上是实际生成器函数的语法糖,基本上是这样的:

def generatorfun(iterable):if len(iterable) == 0:raise StopIterationelse:return (iterable[0], lambda:generatorfun(iterable[1:]))

请记住,这只是伪代码,Python中生成器的实际实现更为复杂。 但是,作为练习以了解发生了什么,请尝试使用连续传递样式来实现生成器对象,而不使用yield关键字。


#6楼

这是yield的心理印象。

我喜欢将线程视为具有堆栈(即使未以这种方式实现)。

调用普通函数时,它将其局部变量放在堆栈上,进行一些计算,然后清除堆栈并返回。 再也看不到其局部变量的值。

使用yield函数,当其代码开始运行时(即,在调用该函数之后,返回生成器对象,然后调用next()方法的生成器对象),它类似地将其局部变量放入堆栈中并进行一段时间的计算。 但是,当它命中yield语句时,在清除堆栈的一部分并返回之前,它会对其局部变量进行快照并将其存储在生成器对象中。 它还在代码中写下了当前位置(即特定的yield语句)。

因此,这是生成器挂起的一种冻结函数。

随后调用next() ,它将函数的所有物检索到堆栈上并对其进行动画处理。 该函数从中断处继续进行计算,而忽略了它刚刚在冷库中度过了一个永恒的事实。

比较以下示例:

def normalFunction():returnif False:passdef yielderFunction():returnif 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_codegi_frame字段是冻结状态的存储位置。 用dir(..)探索它们,我们可以确认我们上面的思维模型是可信的。


#7楼

从编程的角度来看,迭代器被实现为thunk 。

为了将迭代器,生成器和线程池实现为并发执行等,作为重击(也称为匿名函数),人们使用发送到具有分派器的闭包对象的消息,然后分派器对“消息”做出响应。

http://en.wikipedia.org/wiki/Message_passing

next ”是发送到闭包的消息,由“ iter ”调用创建。

有很多方法可以实现此计算。 我使用了变异,但是通过返回当前值和下一个生成器,很容易做到无变异。

这是一个使用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) '(ab)) 'OK -> (stream 'yield) 'a -> (stream 'yield) 'b -> (stream 'yield) 'END -> (stream 'yield) 'END -> 

#8楼

这是一个简单的示例:

def isPrimeNumber(n):print "isPrimeNumber({}) call".format(n)if n==1:return Falsefor x in range(2,n):if n % x == 0:return Falsereturn Truedef primes (n=1):while(True):print "loop step ---------------- {}".format(n)if isPrimeNumber(n): yield nn += 1for n in primes():if n> 10:breakprint "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保持程序流的位置,而下一个循环从“ yield”位置开始。 好像它在那个位置上等待,就在那之前,在外面返回一个值,下一次继续工作。

这似乎是一种有趣而又不错的能力:D


#9楼

尽管很多答案都说明了为什么要使用yield来创建生成器,但是yield有更多用途。 创建协程非常容易,这使信息可以在两个代码块之间传递。 我不会重复任何关于使用yield生成生成器的出色示例。

为了帮助理解以下代码中的yield ,您可以用手指在任何具有yield代码中跟踪循环。 每一个手指打的时候yield ,你必须等待一个nextsend到输入。 当next被调用,您通过跟踪代码,直到你遇到yield ...在右边的代码yield进行评估,并返回给调用者...那你就等着。 再次调用next时,您将通过代码执行另一个循环。 但是,您会注意到,在协程中, yield也可以与send一起使用,它将从调用方将值发送 yielding函数。 如果给出了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()

#10楼

yield就像return一样-它返回您告诉的任何内容(作为生成器)。 不同之处在于,下次调用生成器时,执行从最后一次调用到yield语句开始。 与return不同的是,在产生良率时不会清除堆栈帧,但是会将控制权转移回调用者,因此下次调用该函数时,其状态将恢复。

对于您的代码,函数get_child_candidates的行为就像一个迭代器,因此当您扩展列表时,它一次将一个元素添加到新列表中。

list.extend调用迭代器,直到耗尽为止。 对于您发布的代码示例,返回一个元组并将其附加到列表中会更加清楚。


#11楼

它正在返回发电机。 我对Python并不是特别熟悉,但是如果您熟悉C#的迭代器块 ,我相信它与C#的迭代器块一样 。

关键思想是,编译器/解释器/无论做什么都做一些技巧,以便就调用者而言,他们可以继续调用next(),并且将继续返回值- 就像Generator方法已暂停一样 。 现在显然您不能真正地“暂停”方法,因此编译器构建了一个状态机,供您记住您当前所​​在的位置以及局部变量等的外观。 这比自己编写迭代器要容易得多。


#12楼

这样想:

迭代器只是一个具有next()方法的对象的美化名词。 因此,产生收益的函数最终是这样的:

原始版本:

def some_function():for i in xrange(4):yield ifor 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 += 1if self.count < 4:return self.countelse:# A StopIteration exception is raised# to signal that the iterator is done.# This is caught implicitly by the 'for' loop.raise StopIterationdef some_func():return it()for i in some_func():print i

为了更深入地了解幕后发生的情况,可以将for循环重写为:

iterator = some_func()
try:while 1:print iterator.next()
except StopIteration:pass

这是否更有意义,还是会让您更加困惑? :)

我要指出,这为了说明的目的过于简单化。 :)


#13楼

要了解什么是yield ,您必须了解什么是发电机 。 而且,在您了解生成器之前,您必须了解iterables

可迭代

创建列表时,可以一一阅读它的项目。 逐一读取其项称为迭代:

>>> 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...上使用“ for... in... ”的所有内容都是可迭代的; listsstrings ,文件...

这些可迭代的方法很方便,因为您可以随意读取它们,但是您将所有值都存储在内存中,当拥有很多值时,这并不总是想要的。

发电机

生成器是迭代器,一种迭代, 您只能迭代一次 。 生成器不会将所有值存储在内存中, 它们会即时生成值

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

除了使用()代替[]之外,其他操作都相同。 但是,您不能第二次for i in mygenerator执行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 the distance is ok, return the next childif 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 childif 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 listnode = candidates.pop()# Get the distance between obj and the candidatedistance = node._get_dist(obj)# If distance is ok, then you can fill the resultif 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 candidatecandidates.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()方法是一个列表对象方法,该方法需要可迭代并将其值添加到列表中。

通常我们将一个列表传递给它:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

但是在您的代码中,它得到了一个生成器,这很好,因为:

  1. 您不需要两次读取值。
  2. 您可能有很多孩子,并且您不希望所有孩子都存储在内存中。

它之所以有效,是因为Python不在乎方法的参数是否为列表。 Python期望可迭代,因此它将与字符串,列表,元组和生成器一起使用! 这就是所谓的鸭子输入,这是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,您最好的朋友

itertools模块包含用于操纵可迭代对象的特殊功能。 曾经希望复制一个发电机吗? 连锁两个发电机? 用一个单行将嵌套列表中的值分组? 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__()方法)的过程。 可迭代对象是可以从中获取迭代器的任何对象。 迭代器是使您可以迭代的对象。

本文中有更多关于for循环如何工作的内容 。


#14楼

这是简单语言的示例。 我将提供高级人类概念与低级Python概念之间的对应关系。

我想对数字序列进行运算,但是我不想为创建该序列而烦恼自己,我只想着重于自己想做的运算。 因此,我执行以下操作:

  • 我打电话给你,告诉你我想要一个以特定方式产生的数字序列,让您知道算法是什么。
    该步骤对应于def进不去发电机功能,即包含在功能yield
  • 稍后,我告诉您,“好,准备告诉我数字的顺序”。
    此步骤对应于调用生成器函数,该函数返回生成器对象。 请注意,您还没有告诉我任何数字。 你只要拿起纸和铅笔。
  • 我问你,“告诉我下一个号码”,然后你告诉我第一个号码; 之后,您等我问您下一个电话号码。 记住您的位置,已经说过的电话号码以及下一个电话号码是您的工作。 我不在乎细节。
    此步骤对应于在生成器对象上调用.next()
  • …重复上一步,直到…
  • 最终,您可能会走到尽头。 你不告诉我电话号码; 您只是大声喊道:“抱马!我做完了!没有数字了!”
    此步骤对应于生成器对象结束其工作,并引发StopIteration异常 。生成器函数不需要引发异常。 函数结束或发出return时,它将自动引发。

生成器就是这样做的(包含yield的函数); 它开始执行,在执行yield时暂停,并在询问.next()值时从上一个点继续。 根据设计,它与Python的迭代器协议完美契合,该协议描述了如何顺序请求值。

迭代器协议最著名的用户是Python中的for命令。 因此,无论何时执行以下操作:

for item in sequence:

sequence是如上所述的列表,字符串,字典还是生成器对象都没有关系; 结果是相同的:您从一个序列中逐个读取项目。

需要注意的是def进不去其中包含一个函数yield关键字不是产生一个生成的唯一途径; 这是创建一个的最简单的方法。

有关更准确的信息,请阅读Python文档中有关迭代器类型 , yield语句和生成器的信息。


#15楼

还有另外一件事要提及:yield的函数实际上不必终止。 我写了这样的代码:

def fib():last, cur = 0, 1while True: yield curlast, cur = cur, last + cur

然后我可以在其他代码中使用它:

for f in fib():if some_condition: breakcoolfuncs(f);

它确实有助于简化某些问题,并使某些事情更易于使用。


#16楼

了解yield捷径

当您看到带有yield语句的函数时,请应用以下简单技巧,以了解将发生的情况:

  1. 在函数开始处插入一行result = []
  2. 将每个yield expr替换为result.append(expr)
  3. 在函数的底部插入行return result
  4. 是的-没有更多的yield声明! 阅读并找出代码。
  5. 将功能与原始定义进行比较。

这个技巧可能会让您对函数背后的逻辑有所了解,但是yield实际发生的情况与基于列表的方法发生的情况显着不同。 在许多情况下,yield方法也将大大提高内存效率和速度。 在其他情况下,即使原始函数运行正常,此技巧也会使您陷入无限循环。 请继续阅读以了解更多信息...

不要混淆您的Iterable,Iterators和Generators

首先, 迭代器协议 -当您编写时

for x in mylist:...loop body...

Python执行以下两个步骤:

  1. 获取mylist的迭代器:

    调用iter(mylist) ->这将返回带有next()方法的对象(或Python 3中的__next__() )。

    [这是大多数人忘记告诉您的步骤]

  2. 使用迭代器遍历项目:

    保持调用next()从第1步中从返回的返回值的迭代方法next()被分配给x ,并执行循环体。 如果从next()内部引发异常StopIteration ,则意味着迭代器中没有更多值,并且退出了循环。

事实是Python随时想要遍历对象的内容时都执行上述两个步骤-因此它可能是for循环,但也可能是诸如otherlist.extend(mylist)类的代码(其中otherlist是Python列表) 。

mylist在这里是可迭代的,因为它实现了迭代器协议。 在用户定义的类中,可以实现__iter__()方法以使您的类的实例可迭代。 此方法应返回迭代器 。 迭代器是具有next()方法的对象。 可以在同一个类上同时实现__iter__()next() ,并使__iter__()返回self 。 这将适用于简单的情况,但当您希望两个迭代器同时在同一个对象上循环时,则无法使用。

这就是迭代器协议,许多对象都实现了该协议:

  1. 内置列表,字典,元组,集合,文件。
  2. 用户定义的实现__iter__()
  3. 发电机。

请注意, for循环不知道它要处理的是哪种对象-它仅遵循迭代器协议,并且很高兴在调用next()逐项获取对象。 内置列表一一返回它们的项,字典一一返回 ,文件一一返回 ,依此类推。生成器返回……这就是yield的地方:

def f123():yield 1yield 2yield 3for item in f123():print item

如果您在f123()有三个return语句,则不会产生yield语句,只有第一个会执行,该函数将退出。 但是f123()不是普通函数。 调用f123() ,它不会返回yield语句中的任何值! 它返回一个生成器对象。 另外,该函数并没有真正退出-进入了挂起状态。 当for循环尝试循环生成器对象时,该函数从先前返回的yield后的下一行开始从其挂起状态恢复,执行下一行代码(在本例中为yield语句),并将其返回为下一项。 这会一直发生,直到函数退出,此时生成器将引发StopIteration ,然后退出循环。

因此,生成器对象有点像适配器-通过展示__iter__()next()方法以保持for循环满意,它展示了迭代器协议。 但是,在另一端,它恰好运行该函数以从中获取下一个值,并将其放回暂停模式。

为什么使用发电机?

通常,您可以编写不使用生成器但实现相同逻辑的代码。 一种选择是使用我之前提到的临时列表“技巧”。 这并非在所有情况下都可行,例如,如果您有无限循环,或者当您的列表很长时,这可能会导致内存使用效率低下。 另一种方法是实现一个新的可迭代类SomethingIter ,该类将状态保留在实例成员中,并在next() (或Python 3中的__next__() )方法中执行下一个逻辑步骤。 根据逻辑, next()方法内的代码可能最终看起来非常复杂,并且容易出现错误。 在这里,发电机提供了一种干净而简单的解决方案。


#17楼

还有另一个yield用法和含义(自Python 3.3起):

yield from <expr>

PEP 380-委托给子生成器的语法

提出了一种语法,供生成器将其部分操作委托给另一生成器。 这允许包含“ yield”的一段代码被分解出来并放置在另一个生成器中。 此外,允许子生成器返回一个值,并且该值可用于委派生成器。

当一个生成器重新产生由另一个生成器生成的值时,新语法还为优化提供了一些机会。

此外, 这将引入(自Python 3.5起):

async def new_coroutine(data):...await blocking_action()

以避免将协程与常规生成器混淆(两者均使用了今天的yield )。


#18楼

yield就像函数的返回元素一样。 不同之处在于, yield元素将函数转换为生成器。 生成器的行为就像一个函数,直到“屈服”为止。 生成器停止运行,直到下一次调用为止,并从与启动完全相同的点继续运行。 您可以通过调用list(generator())获得一个包含所有“屈服”值的序列。


#19楼

yield关键字在Python中有什么作用?

答案大纲/摘要

  • 具有yield的函数在调用时将返回Generator 。
  • 生成器是迭代器,因为它们实现了迭代器协议 ,因此您可以对其进行迭代。
  • 也可以生成器发送信息 ,使其在概念上成为协程
  • 在Python 3中,您可以使用yield from从一个生成器在两个方向上将其委派给另一生成器。
  • (附录对几个答案进行了评论,包括最上面的一个,并讨论了生成器中return的用法。)

发电机:

yield仅在函数定义内部是合法的,并且yield包含在函数定义中使其返回生成器。

生成器的想法来自具有不同实现方式的其他语言(请参见脚注1)。 在Python的Generators中,代码的执行会在收益率点冻结 。 调用生成器时(下面将讨论方法),恢复执行,然后冻结下一个收益率。

yield提供了一种实现迭代器协议的简便方法, 该协议由以下两种方法定义: __iter__next (Python 2)或__next__ (Python 3)。 这两种方法都使对象成为一个迭代器,您可以使用collections模块中的Iterator 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一个功能是,一旦耗尽 ,您就无法重用或重置它:

>>> 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_interestif received:deposited += received>>> my_account = bank_account(1000, .05)

首先,我们必须使用内置函数next来使生成器排队。 根据您使用的Python版本,它将调用适当的next__next__方法:

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

现在,回想一下yield from在Python 3中可用。这使我们可以将协程委托给子协程:

def money_manager(expected_rate):under_management = yield     # must receive deposited valuewhile True:try:additional_investment = yield expected_rate * under_management if additional_investment:under_management += additional_investmentexcept 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 managermanager.send(deposited)while True:try:yield from managerexcept 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 "<stdin>", line 4, in <module>File "<stdin>", line 2, in <module>
ValueError

结论

我相信我已经涵盖了以下问题的各个方面:

yield关键字在Python中有什么作用?

事实证明, yield很大。 我相信我可以为此添加更详尽的示例。 如果您想要更多或有建设性的批评,请在下面评论中告诉我。


附录:

对最佳/可接受答案的评论**

  • 仅以列表为例,它对使可迭代的内容感到困惑。 请参阅上面的参考资料,但总而言之:iterable具有返回iterator__iter__方法。 迭代器提供.next (Python 2或.__next__ (Python 3))方法,该方法由for循环隐式调用,直到引发StopIteration为止,一旦这样做,它将继续这样做。
  • 然后,它使用生成器表达式来描述什么是生成器。 由于生成器只是创建迭代器的一种简便方法,因此它只会使问题感到困惑,而我们仍未达到yield部分。
  • 控制发电机的排气中,他调用.next方法,而应改为使用内置函数next 。 这将是一个适当的间接层,因为他的代码在Python 3中不起作用。
  • Itertools? 这与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

由于yield是一种表达,因此尽管没有特别好的用例,但有人认为它可以用于理解或生成器表达中。

CPython核心开发人员正在讨论弃用其津贴 。 这是邮件列表中的相关帖子:

2017年1月30日19:05,布雷特·坎农写道:

2017年1月29日星期日,克雷格·罗德里格斯(Craig Rodrigues)在星期日写道:

两种方法我都可以。 恕我直言,把事情留在Python 3中是不好的。

我的投票是SyntaxError,因为您没有从语法中得到期望。

我同意这对我们来说是一个明智的选择,因为依赖当前行为的任何代码确实太聪明了,无法维护。

在到达目的地方面,我们可能需要:

  • 3.7中的语法警告或弃用警告
  • 2.7.x中的Py3k警告
  • 3.8中的SyntaxError

干杯,尼克。

-Nick Coghlan | gmail.com上的ncoghlan | 澳大利亚布里斯班

此外,还有一个悬而未决的问题(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属性。

脚注

  1. 提案中引用了CLU,Sather和Icon语言,以将生成器的概念引入Python。总体思路是,一个函数可以维护内部状态并根据用户的需要产生中间数据点。这有望在性能上优于其他方法,包括Python线程 ,该方法甚至在某些系统上不可用。

  2. 例如,这意味着xrange对象(Python 3中的range )不是Iterator ,尽管它们是可迭代的,因为它们可以重复使用。像列表一样,它们的__iter__方法返回迭代器对象。

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


#20楼

就像每个答案所暗示的那样, yield用于创建序列生成器。 它用于动态生成一些序列。 例如,在网络上逐行读取文件时,可以按以下方式使用yield函数:

def getNextLines():while con.isOpen():yield con.read()

您可以在代码中使用它,如下所示:

for line in getNextLines():doSomeThing(line)

执行控制转移陷阱

执行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

将打印

"first time"
"second time"
"third time"
"Now some useful value 12"

#21楼

产量是一个对象

return在一个函数会返回一个值。

如果希望函数返回大量值 ,请使用yield

更重要的是, yield是一个障碍

就像CUDA语言中的barrier一样,它在完成之前不会转移控制权。

也就是说,它将从头开始在函数中运行代码,直到达到yield为止。 然后,它将返回循环的第一个值。

然后,其他所有调用将再次运行您在函数中编写的循环,返回下一个值,直到没有任何值可返回为止。


#22楼

yield关键字只是收集返回的结果。 将yield视为return +=


#23楼

这是一种用于计算斐波那契数列的基于yield的简单方法,解释如下:

def fib(limit=50):a, b = 0, 1for i in range(limit):yield ba, b = b, a+b

当您将其输入到REPL中并尝试调用它时,您将得到一个神秘的结果:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

这是因为yield的存在向Python发出信号,表示您想要创建一个generator ,即一个按需生成值的对象。

那么,如何生成这些值? 这可以通过使用内置函数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 fibresults.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并暂停。 为了更好地说明这一点,让我们使用一些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 valueprint("Let's go through it again.")

现在,输入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循环)。


#24楼

(我下面的回答仅从使用Python生成器的角度讲,而不是生成器机制的基础实现 ,它涉及堆栈和堆操作的一些技巧。)

当使用yield而不是python函数中的return时,该函数将变成一种特殊的generator function 。 该函数将返回generator类型的对象。 yield关键字是一个标志,用于通知python编译器专门对待此类功能。 普通函数将在返回一些值后终止。 但是在编译器的帮助下, 可以将 generator函数视为可恢复的。 也就是说,将恢复执行上下文,并且将从上次运行继续执行。 在显式调用return之前,它将引发StopIteration异常(这也是迭代器协议的一部分),或到达函数的结尾。 我发现了很多关于引用的generator ,但这一个从functional programming perspective是最消化的。

(现在,我想根据自己的理解来讨论generator的基本原理,以及iterator 。我希望这可以帮助您掌握迭代器和generator的基本动机 。这种概念也出现在其他语言中,例如C#。)

据我了解,当我们要处理一堆数据时,通常先将数据存储在某个地方,然后再逐一处理。 但是这种幼稚的方法是有问题的。 如果数据量巨大,则预先存储它们是很昂贵的。 因此,为什么不间接存储某种类型的metadata ,而不是直接存储data本身,即the logic how the data is computed

有两种包装此类元数据的方法。

  1. 面向对象的方法,我们将元数据包装as a class 。 这就是实现迭代器协议的所谓iterator器(即__next__()__iter__()方法)。 这也是常见的迭代器设计模式 。
  2. 在函数方法中,我们将元数据包装as a function 。 这就是所谓的generator function 。 但实际上,返回的generator object仍然IS-A迭代器,因为它也实现了迭代器协议。

无论哪种方式,都会创建一个迭代器,即某个可以为您提供所需数据的对象。 OO方法可能有点复杂。 无论如何,要使用哪一个取决于您。


#25楼

TL; DR

代替这个:

def square_list(n):the_list = []                         # Replacefor x in range(n):y = x * xthe_list.append(y)                # thesereturn the_list                       # lines

做这个:

def square_yield(n):for x in range(n):y = x * xyield y                           # with this one.

当你发现自己从头开始建立一个列表, yield每片来代替。

这是我第一次屈服。


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

不同的行为:

收益是单次通过 :您只能迭代一次。 当一个函数包含一个yield时,我们称其为Generator函数 。 而迭代器就是它返回的内容。 这些术语在揭示。 我们失去了容器的便利性,但获得了按需计算且任意长的序列的功效。

产量懒惰 ,推迟了计算。 当您调用函数时,其中包含yield的函数实际上根本不会执行。 它返回一个迭代器对象 ,该对象记住它从何处中断。 每次您在迭代器上调用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因为两种含义都适用:

产量 —生产或提供(如在农业中)

...提供系列中的下一个数据。

屈服 —让步或放弃(如在政治权力中一样)

...放弃CPU执行,直到迭代器前进。


#26楼

许多人使用return而不是yield ,但在某些情况下, yield会更有效且更容易使用。

这是一个绝对适合的yield示例:

返回 (函数中)

import randomdef return_dates():dates = [] # With 'return' you need to create a list then return itfor 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()提供了一个生成器。

现实生活中的例子可能是像逐行读取文件,或者只是想生成一个生成器。


#27楼

总而言之, 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 = 0range = []while n < i:range.append(n)n = n + 1return range

像这样使用

for i in myRangeNaive(10):print i

但这是低效率的,因为

  • 您创建只使用一次的数组(这会浪费内存)
  • 这段代码实际上在该数组上循环了两次! :(

幸运的是,Guido和他的团队足够慷慨地开发发电机,因此我们可以做到这一点。

def myRangeSmart(i):n = 0while n < i:yield nn = n + 1returnfor i in myRangeSmart(10):print i

现在,在每次迭代时,生成器上称为next()的函数都会执行该函数,直到它到达“ yield”语句为止,在该语句中它停止并“ yield”值或到达函数的结尾。 在这种情况下,在第一次调用时, next()执行到yield语句并产生yield'n',在下一次调用时,它将执行递增语句,跳回到'while',对其求值,如果为true,则执行将停止并再次产生“ n”,它将继续这种方式,直到while条件返回false且生成器跳至函数末尾。


#28楼

一个简单的例子来了解它是什么: yield

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

输出为:

1 2 1 2 1 2 1 2

#29楼

所有好的答案,但是对于新手来说有点困难。

我认为您已经了解了return语句。

打个比方, returnyield是双胞胎。 return表示“返回并停止”,而yield表示“返回但继续”

  1. 尝试获取带有return的num_list。
def num_list(n):for i in range(n):return i

运行:

In [5]: num_list(3)
Out[5]: 0

看,您只会得到一个数字,而不是列表。 return永远不会让你高高兴兴,只执行一次就退出。

  1. yield

yield替换return

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

  1. 我们可以再执行一步,用return重写yield语句
In [15]: def num_list(n):...:     result = []...:     for i in range(n):...:         result.append(i)...:     return resultIn [16]: num_list(3)
Out[16]: [0, 1, 2]

这是yield的核心。

列表return输出和对象yield输出之间的区别是:

您将始终从列表对象获取[0,1,2],但只能从“对象yield输出”中检索一次。 因此,它具有一个新的名称generator对象,如Out[11]: <generator object num_list at 0x10327c990>

总之,作为一个隐喻,它可以:

  • returnyield是双胞胎
  • listgenerator是双胞胎

#30楼

yield关键字简化为两个简单事实:

  1. 如果编译器在函数内部的任何位置检测到yield关键字,则该函数不再通过return语句return相反 ,它立即返回一个懒惰的“待处理列表”对象,称为生成器
  2. 生成器是可迭代的。 什么是可迭代的 ? 就像listsetrange或字典视图一样,它具有用于以特定顺序访问每个元素内置协议

简而言之: 生成器是一个懒惰的,增量待定的list ,并且yield语句允许您使用函数符号来编程生成器应该逐渐吐出的列表值

generator = myYieldingFunction(...)
x = list(generator)generatorv
[x[0], ..., ???]generatorv
[x[0], x[1], ..., ???]generatorv
[x[0], x[1], x[2], ..., ???]StopIteration exception
[x[0], x[1], x[2]]     donelist==[x[0], x[1], x[2]]

让我们定义一个函数makeRange ,就像Python的range 。 调用makeRange(n)一个生成器:

def makeRange(n):# return 0,1,2,...,n-1i = 0while i < n:yield ii += 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 = 0while i < n:                 #      while i < n:TO_RETURN += [i]         #~         yield ii += 1                   #          i += 1  ## indentedreturn 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关键字来实现。

请注意:生成器实际上可以用于更多事情,例如实现协程或不确定性编程或其他优雅的事情。 但是,我在这里提出的“惰性列表”观点是您会发现的最常见用法。


幕后花絮

这就是“ 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)) 。 那些绝对需要克隆生成器的人(例如,正在进行骇人的骇人的元编程的人)可以在绝对必要的情况下使用itertools.tee ,因为可复制的迭代器Python PEP标准建议已被推迟。

“ yield”关键字有什么作用?相关推荐

  1. “yield”关键字有什么作用?

    问题描述: 想要改进这篇文章?提供这个问题的详细答案,包括引文和解释为什么你的答案是正确的.没有足够细节的答案可能会被编辑或删除. Python 中的 yield 关键字有什么用?它有什么作用? 例如 ...

  2. yield关键字有什么作用

    所属网站分类: python基础 > 语句 作者:goodbody 链接: http://www.pythonheidong.com/blog/article/10/ 来源:python黑洞网  ...

  3. Python生成器实现及yield关键字

    Python生成器实现及yield关键字 我在另一篇文章中介绍了Python迭代器,https://blog.csdn.net/weixin_43790276/article/details/9034 ...

  4. 反编译使用yield关键字的方法

    我认为这是一个真命题:"没有用.NET Reflector反编译并阅读过代码的程序员不是专业的.NET程序员"..NET Reflector强大的地方就在于可以把IL代码反编译成可 ...

  5. C#中使用的yield关键字是什么?

    在" 如何仅显示IList <>的片段"问题中,答案之一具有以下代码片段: IEnumerable<object> FilteredList() {fore ...

  6. python function if yield_Python中的yield关键字

    Python中的yield关键字 这是stackoverflow上一个关于yield关键字的问题以及它被推荐次数最高的一个答案 问题: Python中的yield关键字是什么?它是用来做什么的? 例如 ...

  7. python生成器yield原理_生成器yield关键字详解

    鉴于yield关键字的原理大家理解的都不是很深刻,今天我们主要就这一课题进行探讨. 生成器可以用什么方式得到? 方法一: 利用推导式的方式得到生成器# 列表推导式 list1 = [i for i i ...

  8. Python 生成器 和 yield 关键字

    Python 中 yield 的作用:http://youchen.me/2017/02/10/Python-What-does-yield-do/# Python 生成器详解:http://codi ...

  9. python lambda表达式及用法_Python:lambda表达式和yield关键字理解与使用讲解

    一.lambda表达式 1.1.lambda表达式理解 lambda的主体是一个表达式,而不是一个代码块,仅仅能在lambda表达式中封装有限的逻辑进去.如果要通俗的理解lambda表达式,可以结合C ...

最新文章

  1. 重磅!联合国权威AI趋势报告,美中日韩四分天下
  2. 单链表的前K个的逆序输出
  3. matlab求解复杂复数方程,用matlab求解一个两重积分方程(未知数在积分下限,含复数积分)...
  4. 请列举一下在jvm中哪些对象可以当作root对象?
  5. jquery mobile页面切换效果(Flip toggle switch)(注:jQuery移动使用的数据属性的列表。 )...
  6. 响应式web之@media screen
  7. C语言程序设计谭浩强版 六
  8. linux 监听 ipv6,zabbix 监控 ipv6
  9. UE激活(亲测有效,通过注册机激活)
  10. 【优雅编程之道】之IO流,序列化的4点建议
  11. MaaS无缝出行服务呼之欲出 传统出行模式将被颠覆
  12. 人不行别怪路不平!万丈高楼平地起,要想辉煌靠自己。
  13. 2019-CVPR-上交-(DAIN)Depth-Aware Video Frame Interpolation
  14. 人生历程中的几个观念瓶颈
  15. Linux内核编译基础
  16. CentOS7 安装 Oracle
  17. 密码(mima)的答案
  18. python找到一行单词中最长的_python - 查找.txt文件中最长的单词,不带标点符号 - SO中文参考 - www.soinside.com...
  19. 视频监控P2P解决方案
  20. 程序员也应该多花时间多读书

热门文章

  1. 解决 Unable to get provider
  2. svn TortoiseSVN 回滚版本
  3. 【C++】修改const变量的值
  4. 第八周项目一-数组作数据成员(1)
  5. 牛听听 总是获取音频流出错_【伤感听听|推荐】大度 什么
  6. 【Android基础】 Launch Mode
  7. 简单js特效代码大全_Django 功法大全
  8. 从gitee 下载代码到本地
  9. 金蝶API apiEnv.apiSession.Token = GetToken()报错
  10. Hadoop中RPC协议小例子报错java.lang.reflect.UndeclaredThrowableException解决方法