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

拈花ヽ惹草 提交于 2020-03-22 13:44:01

3 月,跳不动了?>>>

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方法时会发生什么? 是否返回列表? 一个元素? 再叫一次吗? 后续通话何时停止?


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


#1楼

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

作为Python生成器:

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

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)

#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_continuation
    return value

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

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

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


#6楼

解决方案一: “ yield”关键字有什么作用?
解决方案二: What does the “yield” keyword do?
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!