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 。 提出这一修改是为了让用户将数据发送到生成器中。 。 要发送数据, 发送数据时, 就必须将它指定它为某种东西, 。

其他回答

理解产出的快捷键

当您看到带产出语句的函数时,应用这个简单易懂的把戏来理解会发生什么:

在函数开始处插入行结果 = []。 以结果替换每个输出。 附录( 扩展) 。 在函数底部插入一行返回结果 。 耶 - 不再生成语句! 读取并解析代码。 将函数与原始定义比较 。

这个骗局也许能让你了解函数背后的逻辑, 但实际的收益率与列表法中发生的情况大不相同。 在许多情况下, 收益率法会提高记忆效率和速度。 在其他情况下, 这个骗局会让你陷入一个无限的循环, 即使最初的功能运作良好。 阅读以学习更多...

不要弄乱你的循环器 循环器和发电机

首先,当您写作时的循环程序协议

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

Python 执行以下两个步骤:

为我的列表获取一个代号 : 调用 exer( mylist) - > 这返回一个具有下一个( ) 方法( 或 __ next__ () () 在 Python 3 中) 的对象 [这是大多数人忘记告诉你 使用传动器环绕项目的步骤 : 继续调用从第 1 步返回的代名器上的下一个( ) 方法 。 下一个( ) 的返回值被指定给 x , 循环体被执行 。 如果从下一个( ) 中提出例外 停止 , 这意味着在循环器中没有更多的值, 循环被退出 。

真相是 Python 执行上述两个步骤, 每当它想绕过对象的内容时, 都会执行上述两个步骤 - 所以它可以是环绕, 但它也可以像其它列表一样是代码 。 extendend( mylist) ( 其中其他列表是 Python 列表 ) 。

这里的我的列表是可替换的, 因为它执行的是循环协议 。 在用户定义的类别中, 您可以使用 ` iter__ () 方法使分类的循环性实例可以被使用。 此方法应该返回一个循环器。 循环器是一个带有下一个( ) 方法的对象。 在同一类中可以同时执行 _ iter__ () 和 下一个( ) , 并有 _ iter__ () 返回自我 。 这将对简单案例有效, 但当您想要两个循环器同时绕过同一个对象时则不行 。

这就是传动程序,许多物体执行这个程序:

内置列表、 词典、 图普尔、 集和文件。 执行 ` iter__ () 的用户定义的分类 。 发电机 。

注意“ 循环” 并不知道它所处理的物体是什么类型 - 它只是遵循了循环程序, 并且乐意在下一个( ) 调用时按项目逐项获得项目 。 内建列表逐项返回项目, 字典逐项返回关键词, 文件逐行返回行等 。 而发电机则返回... 也就是产出来源所在 :

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

而不是输出语句, 如果您在 f123 () 中有三个返回语句, 只有第一个将被执行, 而函数会退出 。 但是 f123 () 并不是普通函数 。 当调用 f123 () 时, 它不会返回产值语句中的任何值 。 它返回一个生成对象 。 另外, 该函数并不真正退出 - 它会进入一个中止状态 。 当循环尝试在生成对象上循环时, 函数会从先前返回的产值之后的下一行的中止状态恢复到下一行的状态, 执行下一行代码, 在此情况下, 产生语句, 并返回为下一个项目 。 这一直发生到函数退出, 此时, 生成器将启动暂停, 以及循环退出 。

因此,生成器对象有点像一个适配器 — — 在一端,它展示了迭代程序, 暴露了 `iter___ () 和下一个 () 方法来保持循环的快乐。 但是,在另一端, 它运行着功能, 足以将下一个值调出, 并把它放回中止模式 。

为什么使用发电机?

通常情况下, 您可以写入不使用发电机的代码, 但执行相同的逻辑。 一个选项是使用我之前提到的临时列表“ trick ” 。 这不会在所有情况下都有效, 比如, 如果您有无限环, 或者当您有非常长的列表时它可能无效地使用内存 。 另一种方法是执行一个新的可循环的类别“ 某些东西 ” , 将国家保留在成员中, 并在下一个( ) ( 或 Python 3 ) 方法中执行下一个逻辑步骤 。 根据逻辑, 下一个( ) 方法中的代码可能最终会查找非常复杂和易被错误的代码 。 在这里, 生成器可以提供一个简单明了的解决方案 。

产量的另一种用途和含义(自Python3.3以来):

yield from <expr>

PEP380 -- -- 属于子发电机的语法:

提议对发电机使用语法将部分操作权下放给另一个发电机。 这样可以将含有“ ield” 的代码部分输入到另一个发电机中。 此外, 允许次发电机返回一个值, 并将价值提供给授权发电机。 新的语法也为当一台发电机再生价值时实现优化开辟了某些机会 。

此外,这将引入(自Python3.5以来):

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

以避免与正常发电机混为一谈(两者均使用每天的产量)。

就像有人要你做5个纸杯蛋糕一样。如果你做了至少一个纸杯蛋糕,你可以在做其他蛋糕时给他们吃。

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

这里称为发电机, 它会做蛋糕。 如果您叫作 Make_ 函数, 您可以得到一个发电机, 而不是运行此函数。 这是因为当输出关键字出现在一个函数中时, 它会变成一个生成器 。

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

他们消耗了所有的蛋糕, 但他们又要求一个。

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

并且他们被告知不要问更多的问题。 所以一旦你消耗了发电机, 你就会用它做。 如果你想要更多的蛋糕,你需要再打电话做蛋糕。 这就像再订一份蛋糕。

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

您也可以使用上面的生成器来循环。

举个例子:假设你每次问密码时都想要随机密码。

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

这里的 rpg 是一个生成器, 它可以生成无限数量的随机密码。 所以我们也可以说, 当我们不知道序列的长度时, 生成器是有用的, 不同于列表, 列表中有一定数量的元素 。

一个容易理解它是什么的简单例子:产量

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


for i in f123():
    print (i)

产出是:

1 2 1 2 1 2 1 2

通常情况下, 它会用来创建一个不起作用的代名词。 将“ ield” 当作您函数的附加件, 以及您作为数组的函数。 如果符合某些标准, 您可以在函数中添加此值, 使之成为代名词 。

arr=[]
if 2>0:
   arr.append(2)

def func():
   if 2>0:
      yield 2

两者的输出结果相同。

使用产量的主要优势是创建迭代器。 迭代器在即时计算时不会计算每个项目的价值。 它们只在您要求时才计算。 这被称为懒惰评价 。