Python 中产出关键字的用法是什么? 它能做什么?

例如,我试图理解这个代码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_camedates 被调用时会怎样? 列表是否返回? 单一个元素吗? 是否再次调用? 以后的电话何时停止?


1. 本代码由Jochen Schulz(jrschulz)编写,他为公制空间制作了一个伟大的Python图书馆,与完整的源:模块mspace链接。


当前回答

Python 的输出关键字是做什么的 ?

答复大纲/摘要

函数, 调用时, 返回生成器。 发电机是循环器, 因为它们执行循环程序, 以便您可以对它进行循环。 也可以发送一个发电机信息, 使其在概念上成为共同的常规。 在 Python 3 中, 您可以将一个发电机从一个发电机到另一个发电机, 从两个方向调用。 (附录: 包括顶部的答案在内的几个答案, 并讨论在发电机中使用返回的方法 。)

发电机:

收益率只是功能定义中的法律内涵,而将收益率列入功能定义使其返回产生者。

发电机的想法来自其他语言(见脚注1),其实施方式各有不同。 在Python的发电机中,代码的执行在生产点被冻结。当发电机被调用(方法在下文讨论)时,再恢复执行,然后冻结在下一个生产点。

输出提供了执行循环协议的简单方法,由以下两种方法定义:__iter__和__ext_。这两种方法都使对象成为可与收藏模块的Exerator摘要基础类进行打印的复制器。

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__
True                           # implements the iterator protocol.

生成器类型是一个子迭代器类型 :

from types import GeneratorType
from collections.abc import Iterator

>>> issubclass(GeneratorType, Iterator)
True

如有必要,我们可以这样打字检查:

>>> isinstance(gen, GeneratorType)
True
>>> isinstance(gen, Iterator)
True

迭代器的一个特征是,一旦耗竭,您无法再利用或重置它:

>>> 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, 您可以使用以下来源的产量:

def func(an_iterable):
    yield from an_iterable

但是,也允许向次级发电机授权,这一点将在下一节 " 与次级水泥合作授权 " 中加以解释。

计票:

窗体中显示一个表达式,该表达式允许将数据发送到生成器(见脚注3)

以下是一个例子,请注意收到的变量,该变量将指向发送到生成方的数据:

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)

首先, 我们必须排队, 下一个是内建函数 。 它会调用合适的下一个或 下一步方法, 取决于您使用的 Python 版本 :

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

现在我们可以将数据发送到生成器。 (“终结者”和“下一个”是一样的 ) :

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

合作社代表团到分科诊所分科

现在,请记住,Python 3的产量是可以得到的。 这使得我们可以将共同路线 委托给一个子烹饪:


def money_manager(expected_rate):
    # must receive deposited value from .send():
    under_management = yield                   # yield None to start.
    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'''
            raise
        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'''
    # must queue up manager:
    next(manager)      # <- same as manager.send(None)
    # This is where we send the initial deposit to the manager:
    manager.send(deposited)
    try:
        yield from manager
    except GeneratorExit:
        return manager.close()  # delegate?

现在我们可以将功能委托给一个子生成器 并且它可以被一个发电机使用 就像上面那样:

my_manager = money_manager(.06)
my_account = investment_account(1000, my_manager)
first_year_return = next(my_account) # -> 60.0

现在模拟在账户中再增加1000, 加上账户的回报( 60.0 ) :

next_year_return = my_account.send(first_year_return + 1000)
next_year_return # 123.6

从PEP 380中,您可以阅读更多关于产量的确切语义。

其他方法:关闭和投掷

关闭方法在功能执行被冻结时提升发电机输出。 也可以被 __ del__ 调用, 这样您就可以设置任何清理代码, 用于处理发电机输出 :

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 6, in money_manager
  File "<stdin>", line 2, in <module>
ValueError

结论 结论 结论 结论 结论

我认为,我已处理了下列问题的所有方面:

Python 的输出关键字是做什么的 ?

事实证明,产量是很大的。我相信我可以为此再增加更详尽的例子。如果你需要更多的或有建设性的批评,请在下面评论,让我知道。


附录:

顶级/接受的答复的优先程度**

使用列表作为示例。 参见我上面的引用, 但概括地说: 循环含有 ` irit_ 的方法返回一个迭代器。 一个迭代器另外提供了一种 . next_ 的方法, 以循环为暗号, 以循环为代号, 直到它升起 停止 试运行, 一旦它确实升起 停止 试运行, 它会继续这样做 。 然后它会使用一个发电机表达方式来描述一个发电机。 由于一个发电机表达方式只是创建一个代用器的方便方式, 它只会混淆物质, 而我们还没有到达产值部分 。 在控制发电机耗竭时, 他调用 . next 方法( 只在 Python 2 中有效 ) , 而不是使用 内建函数, 下一步。 调用下一个 (obj) 将是一个适当的间接层, 因为他的代码在 Python 3. Itertools 中不起作用 。 这与结果完全无关 。 没有讨论 与 Python 3 中产生新功能收益的方法提供的方法和 Python 。

上方/接受的回答是一个非常不完整的回答。

回答的精度表示在发电机的表达或理解中产生产量。

语法目前允许列表理解中的任何表达式 。

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时,布雷特坎农写道:在太阳上,2017年1月29日,16:39克雷格·罗德里格斯写道:我同意这两种方法。把事情保留在Python 3的状态是不对的,IMHO。我的投票是语法错误,因为你没有得到你期望的语法。我同意这对我们来说是一个明智的结局,因为任何依赖当前行为的代码都非常聪明,无法维持。在到达那里时,我们可能想要:在2.7的Py3k警告中,用3.7的Py3k警告来表示警告或破坏警告。x语法错误,Nick。 -- Nick Coghlan ncoghlan at gmail.com {Brisbane,澳大利亚,Gmail. com {Brisbane。

此外,还有一个未决问题(10544)似乎指向从来就不是一个好主意(PyPy, PyPy, 写在Python的Python执行文件,

底线,直到CPython的开发者告诉我们别的情况: 不要在生成器表达或理解中放出产量。

发电机中的回程声明

在Python 3 中:

在发电机函数中, 返回语句表示发电机已完成, 并将导致 StopLiveration 上升。 返回的值( 如果有的话) 用作构建 StopLiveration 的参数, 并成为 StopIturation. value 属性 。

Python 2 中的历史注释 : “ 在生成器函数中, 返回语句不允许包含表达式_ 列表 。 在此情况下, 光返回表示生成器已经完成, 并将导致停止使用 。 ” 表达式列表基本上是用逗号分隔的任何多个表达式 - 基本上在 Python 2 中, 您可以返回停止生成器, 但无法返回一个值 。

脚注脚注

将生成器的概念引入 Python 的建议中引用了语言 CLU、 Sather 和 图标 。 一般的想法是, 函数可以维持内部状态, 并产生用户需要的中间数据点 。 这承诺在性能上优于其他方法, 包括Python 线性线性, 某些系统中甚至没有这种系统。 这意味着, 范围天体虽然是可循环的, 但却不是迭代器, 因为它们是可以再利用的 。 和列表一样, 它们的 ` eter_ 方法返回替换器对象 。 收益最初是作为声明引入的, 意思是它只能在代码块的线性起始处出现 。 现在, 收益产生一种收益表达方式 。 https://docs. python.org/2/reference/spoint_stmts.html# grammar- token- yeld_stmt 。 提出这一修改是为了让用户将数据发送到生成器中。 。 要发送数据, 发送数据时, 就必须将它指定它为某种东西, 。

其他回答

以下是一些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)

使用关闭物体而不是发电机(因为关闭物体和物体是等效的)

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)

虽然很多答案都表明你为什么用一个产量来创建发电机, 但对于产量来说却有更多的用途。 做一个共同的习惯很容易, 这样可以让信息在两个代码区块之间传递。 我不再重复任何已经给出的关于使用产量来创建发电机的好例子 。

为了帮助理解下一个代码中的产值, 您可以用手指通过任何有产值的代码来追踪循环。 每次你的手指碰到产量的代码时, 您必须等待下一个或发送输入。 调用下一个代码时, 您可以跟踪代码, 直到你到达产量... 产值右侧的代码被评估并返回到调用者... 然后您等待。 当调用下一个代码时, 您会通过代码执行另一个循环。 但是, 您会注意到, 产值也可以用一个发送方式来使用。 发送时, 将会将一个调用器的值发送到产值中 。 如果发送了, 则会收到发送的值, 然后将其从左侧吐出... , 然后通过代码的跟踪直到您再次到达产量( 返回最后的值, 如下一个调用 ) 。

例如:

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

又一个TRL;DR

列表中的迭代器 : 下一个 () 返回列表的下一个元素 。

迭代生成器: 下一个 () 将计算苍蝇上的下一个元素( 执行代码)

您可以通过下拨“无论流量如何复杂”,将产出/生成器视为手动运行外部控制流的一种方式(如继续循环一步),然后调用该输出/生成器作为手动运行外部控制流的一种方式。

注意 : 生成器不是一个正常的函数。 它会像本地变量( stack) 一样记住先前的状态 。 请参看其他答案或文章以详细解释 。 生成器只能重复一次 。 您可以不生产, 但不会是那么好, 所以它可以被视为“ 非常好” 的语言糖 。

也可以将数据发送回生成器!

事实上,正如这里许多答案所解释的那样,利用产量产生一个发电机。

您可以使用产出关键字将数据发送回“实时”生成器。

示例:

假设我们有一种方法可以从英语翻译成其他语言。 在开始的时候, 它会做一些很重的事情, 应该做一次。 我们希望这个方法可以永远运行( 不知道为什么..... . :) , 并且收到要翻译的单词 。

def translator():
    # load all the words in English language and the translation to 'other lang'
    my_words_dict = {'hello': 'hello in other language', 'dog': 'dog in other language'}

    while True:
        word = (yield)
        yield my_words_dict.get(word, 'Unknown word...')

运行中 :

my_words_translator = translator()

next(my_words_translator)
print(my_words_translator.send('dog'))

next(my_words_translator)
print(my_words_translator.send('cat'))

将打印 :

dog in other language
Unknown word...

概括如下:

使用发件人内部发送方法将数据发送回发件人。要允许,使用 a (ield) 。

这里所有的答案都很好,但其中只有一个(最受投票支持的)与你的代码如何运作有关。其他的与一般的发电机有关,也与它们如何运作有关。

所以,我不重复发电机是什么或产量是什么;我认为这些都包含在现有的答案中。然而,在花了几个小时试图理解一个与你的代码相似的代码之后,我将打破它是如何运作的。

您的代码绕过二进制树结构。 让我们以这棵树为例:

    5
   / \
  3   6
 / \   \
1   4   8

另一个简单的二进制搜索树的十字路口:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

执行代码在树形对象上,它执行__iter___这样:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

候选人发言可用树上元素替换; Python 翻译为

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

因为节点. _ iter_ 函数是一个生成器, 内部的代码按迭代执行 。 所以执行会是这样的 :

根元素是第一个; 检查它是否留下了孩子, 并且要循环它们( 因为我们叫它它 1 ) 。 它有一个孩子, 所以执行它。 给孩子自己。 左左为自己创建一个新的循环器 。 左是节点对象本身( it2) 。 左是同一逻辑 2 , 新的循环器已经创建( it3) 。 现在我们到达了树的左端 。 现在我们到达了树的左端。 它3 没有留下孩子, 所以它会继续下去并产生自我。 在下一个呼叫( it3) 时, 它会提高停止作用, 因为它没有正确的孩子( 到达函数的尽头, 但没有产生任何效果) 。 它1 和它2 仍然在活动 - 它们没有耗尽, 调用下一个( it2) 将产生值, 而不是提高停止作用 。 现在我们回到了它的上下文 2 , 并调下一个( it2) 继续它停止它的地方 : 在产生子声明之后 。 由于它没有更多的剩余孩子, 它会继续持续并产生自我 val 。 val 。

这里的渔获是,每次迭代都会产生次标准来绕过树,并保持当前迭代的状态。 一旦它到达终点,它就会绕过堆叠,并按正确的顺序返回值( 最小的收益值首先 ) 。

您的代码示例在一种不同的技术中做了类似的事情: 它为每个孩子输入了一个元素列表, 然后在下一个迭代中, 它弹出它, 并在当前对象上运行函数代码( 也就是自定义 ) 。

我希望这对这个传奇话题有一点帮助,我花了好几个小时来画这个过程来理解它。