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链接。


当前回答

产出关键字简化为两个简单的事实:

如果编译者在函数内的任何地方检测到产出关键字,则该关键字函数不再通过返回语句返回。相反,它会立即返回一个称为生成器的懒惰的“待决列表”对象。“生成器”是可循环的。什么是可循环的?它像列表或设置或范围或编辑视图一样,带有按一定顺序访问每个元素的内置协议。

简言之: 最常见的情况是, 发电机是一个懒惰的、 递增的等待列表, 并且产出语句允许您使用函数符号来编程生成器应该逐渐吐出的列表值。 此外, 高级用法允许您使用发电机作为共程( 见下文 ) 。

generator = myYieldingFunction(...)  # basically a list (but lazy)
x = list(generator)  # evaluate every element into a list

   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

基本上, 只要遇到产出语句, 函数就会暂停并保存状态, 然后根据 Python 传动协议( 在某些合成结构中, 类似反复呼叫下一个( ) 的循环, 并捕捉一个停止作用的例外等) , 发出“ 列表中的下一个返回值 ” 。 您可能遇到过带有生成表达式的生成器; 生成函数更强大, 因为您可以将参数反馈到暂停的生成器功能中, 使用它们来实施 comutines 。 稍后会更多 。


基本示例(“清单”)

我们来定义一个函数,它就像 Python 的射程。 调用 makeRange(n) returns a Generator:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

要强制生成器立即返回其未完成的值, 您可以将它传送到列表 () (就像您可以任意使用 ) :

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

比较“仅返回列表”的示例

上述例子可视为仅仅是创建一份清单,并附在后面并返回:

# return a list                  #  # return a generator
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
    return TO_RETURN             # 

>>> makeRange(5)
[0, 1, 2, 3, 4]

不过,有一个重大差别;见最后一节。


您如何使用发电机

所有发电机都是易变的, 所以它们经常被这样使用:

#                  < ITERABLE >
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

要对发电机有更好的感觉,您可以玩过工具模块( 一定要使用链。 来自_ irightable, 而不是当必要时使用链子 ) 。 例如, 您甚至可以使用生成器来实施无限长的懒惰列表, 比如 ltertools. counts () 。 您可以执行您自己的除法列表( 可认证的) : zip( 计数 ) , 或使用一段时间内生成关键字来这样做 。

请注意: 发电机实际上可以用于更多的事情, 比如实施 comotines 或非 确定性编程或其他优雅的东西。 然而, 我在此介绍的“ 懒惰列表” 观点是您最常用的 。


幕后幕后

这就是“ Python 迭代协议” 的原理。 也就是说, 当您做列表( makeRange(5)) 时会发生什么 。 这就是我前面描述的“ 懒惰、 递增列表 ” 。

>>> x=iter(range(5))
>>> next(x)  # calls x.__next__(); x.next() is deprecated
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__ () 函数, 这是“ 电路协议” 的一部分, 并在所有迭代器中找到 。 您可以手动使用下一个( ) 函数( 和迭代协议的其他部分) 来执行奇特的东西, 通常以降低可读性为代价, 所以尽量避免这样做...


锥体

圆柱形示例:

def interactiveProcedure():
    userResponse = yield makeQuestionWebpage()
    print('user response:', userResponse)
    yield 'success'

coroutine = interactiveProcedure()
webFormData = next(coroutine)  # same as .send(None)
userResponse = serveWebForm(webFormData)

# ...at some point later on web form submit...

successStatus = coroutine.send(userResponse)

comotine (generations 通常接受通过 产出 关键字输入输入 , 例如, 下一个 Input = 产生下一个输出, 作为一种双向通信形式) 基本上是允许暂停自己和请求输入的计算( 例如它下一步应该做什么 ) 。 当 comotine 暂停自己( 当运行中的 comotine 最终会点击 产出 关键字时) , 计算会暂停, 控制会被倒回“ 调” 函数( 要求下一个计算值的框架 ) 。 暂停的发电机/ 库外 仍然暂停, 直到另一个函数( 可能是一个不同的函数/ 文本) 启用后, 请求下一个值( 通常通过输入数据将暂停的逻辑内部输入到 comutine 的代码 ) 。

您可以将皮延共程视为懒惰的递增待决列表, 下一个元素不仅取决于先前的计算, 而且还取决于输入, 您可以选择在生成过程中注射 。


贫提亚e

通常,大多数人不会关心以下的区别,可能想在这里停止阅读。

在 Python-speak 中, 迭代是“ 理解“ 循环概念” 的任何物体, 如列表[ 1, 2, 3] , 转动器是请求循环的具体实例, 如 [ 1, 2, 3,. . _ eter __ () 。 生成器与任何迭代器完全相同, 但它的写法除外( 带有函数语法 ) 。

当您从列表中请求一个迭代器时, 它会创建一个新的迭代器。 但是, 当您从一个迭代器中请求一个迭代器( 您很少会这样做 ) 时, 它只会给您一个副本 。

因此,在不可能的情况下,你没有 做这样的事情...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... 然后记住发电机是一个迭代器, 也就是说, 它是一次性使用。 如果您想要再使用它, 您应该再次调用 MyRange (...) 。 如果您需要使用结果两次, 将结果转换为列表, 并存储在变量 x = 列表( MyRange (5)) 中。 那些绝对需要克隆生成器的人( 例如, 那些正在做可怕的黑客化元程序设计的人) 可以使用它的工具.tee( 仍然在 Python 3 中工作) , 如果绝对需要的话, 因为可复制的迭代器 Python PEP 标准建议已被推迟 。

其他回答

和每个答案一样, 收益被用于创建序列生成器。 它用于动态生成某些序列。 例如, 在按行阅读网络文件行时, 您可以使用以下的收益函数 :

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

您可在您的代码中使用以下代码:

for line in getNextLines():
    doSomeThing(line)

执行控制控制

执行控制将会从 GetNextLines () 转到执行时的循环。 因此, 每次引用 NextLines () 时, 执行都会从上次暂停处开始 。

因此,简言之,一个函数具有以下代码

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"

(我下面的回答只是从使用Python发电机的角度,而不是从发电机机制的基本实施角度,后者涉及一些堆叠和堆积操纵的伎俩。 )

当在 python 函数中使用 quot 而不是返回时, 该函数将被转换为特殊的名称 。 此函数将返回生成器类型的对象 。 产量关键字是提醒 python 编译器专门处理此函数的旗帜 。 正常函数一旦从中返回某些值就会终止 。 但是, 在编译器的帮助下, 生成器的函数可以被视为可恢复 。 也就是说, 执行环境将会恢复, 执行会从上次运行时继续 。 直到您明确调回, 这会引起一个停止引力例外( 也是循环协议的一部分) , 或者到达函数的终点 。 我发现许多关于生成器的引用, 但从功能编程角度来说, 这是一种最可消化的引用 。

(现在我想谈谈产生者背后的理由, 以及基于我自己的理解的循环器。 我希望这能帮助你掌握循环器和生成者的基本动机。 这一概念以其他语言出现, 如 C# 。 )

据我所知,当我们想要处理一大批数据时,我们通常先在某处储存数据,然后逐个处理。但这种天真的方法有问题。如果数据量很大,那么事先将数据全部储存起来费用很高。因此,与其直接储存数据本身,不如间接储存某种元数据,即数据计算逻辑。

有两种方法可以包扎这类元数据。

OO 方法, 我们把元数据包成一个类。 这是执行循环协议( 即 __ next_ () 和 __ ter_ () 方法) 的所谓迭代器。 这也是常见的迭代器设计模式 。 功能方法, 我们将元数据包成函数 。 这是所谓的生成功能 。 但是在引擎盖下, 返回的生成对象仍然是 IS - A 迭代器, 因为它也执行循环程序 。

无论哪种方式, 都会创建一个迭代器, 即某个可以提供您想要的数据的对象。 OO 处理方式可能有点复杂。 总之, 由您决定使用哪一种 。

我不太熟悉Python, 但我相信它和C#的传动屏障一样, 如果你熟悉这些。

关键的想法是, 编译者/ 解释者/ 不论做什么诡计, 就打电话者而言, 他们可以继续调用下一个 () , 并且它会继续返回数值 - 仿佛生成器方法被暂停。 现在显然你无法真正“ 暂停” 一种方法, 所以编译者可以建立一个状态机器, 以便您记住您目前的位置和本地变量等的外观 。 这比自己写一个转录器容易得多 。

下面是浅白语言的例子。我将提供高层次人类概念与低层次Python概念之间的对应关系。

我想用数字序列操作, 但我不想用这个序列的创建来烦恼我自己, 我只想专注于我想做的操作。 因此, 我做以下工作:

我打电话给你并告诉你,我想要一个以特定方式计算的数字序列,我告诉你算法是什么。 这个步骤对应着定义发电机的函数, 也就是包含一个产出的函数。 稍后我告诉你, “ 好, 准备好告诉我数字的序列 ” 。 这个步骤对应着调用发电机的函数, 返回一个发电机对象。 注意不要告诉我任何数字; 你只是拿起你的纸张和铅笔。 我问你, “ 请告诉我下一个数字 ” , 然后你告诉我第一个数字; 之后, 你等着我问你下一个数字。 这是你的任务, 也就是确定你所在的位置, 你已经说过的数字, 下一个数字是什么。 我不在乎细节。 这个步骤相当于在发电机对象上调用下一个( 发电机) 号码的方法。 ( Python 2, next) 注意, 这是一个发电机对象的方法; 在 Python 3, 它被命名为...

这是生成器所做的( 包含一个产值的函数 ) ; 它开始在第一个( ) 上执行, 当它做一个产值时暂停, 当要求下一个( ) 值时, 它会从最后一点继续 。 它的设计完全符合 Python 的循环协议, 协议描述如何按顺序要求值 。

迭代协议最著名的用户是 Python 的命令用户。 所以, 当你做 :

for item in sequence:

序列是否是一个列表、字符串、字典或上述生成对象并不重要;结果是一样的:您逐个阅读序列中的项目。

请注意,定义含有产出关键字的函数不是创建生成器的唯一方法;它只是创建生成器的最简单的方法。

欲知更准确的信息,请阅读Python文件中的迭代机类型、产量说明和发电机。

输出允许您通过将循环部分乘以一个便于再利用的单独方法来写出更聪明的编剧。

假设你需要环绕电子表格的所有非空白行,对每行都做一些事情。

for i, row in df.iterrows(): #from the panda package for reading excel 
  if row = blank: # pseudo code, check if row is non-blank...
    continue
  if past_last_row: # pseudo code, check for end of input data
    break
  #### above is boring stuff, below is what we actually want to do with the data ###
  f(row)

如果您在类似循环中需要调用 g( row) , 您可能会发现自己重复了对数, 并重复了对数的检查, 有效行的检查是无聊、 复杂和容易出错的。 我们不想重复( DRY 原则 ) 。

您想要将检查每个记录的代码与实际处理行的代码区分开来, 例如 f( row) 和 g( row) 。

您可以设定一个函数, 将 f () 作为输入参数, 但是在一种方法中使用收益率要简单得多, 这种方法可以做所有关于检查有效行的无聊事情, 准备拨打 f () :

def valid_rows():
  for i, row in df.iterrows(): # iterate over each row of spreadsheet
    if row == blank: # pseudo code, check if row is non-blank...
      continue
    if past_last_row: # pseudo code, check for end of input data
      break
    yield i, row

请注意,该方法的每次调用都会返回下一行, 但如果所有行都读取, 并用于结束部分, 方法会正常返回。 下一次调用将开始新的循环 。

现在您可以在数据上写入迭代, 而不必重复对有效行进行无趣的检查( 现在根据自己的方法来计算) , 例如 :

for i, row in valid_rows():
  f(row)

for i, row in valid_rows():
  g(row)

nr_valid_rows = len(list(valid_rows()))

仅此而已。 请注意, 我还没有使用诸如 迭代器、 生成器、 协议、 共同常规等术语 。 我认为这个简单的例子 适用于我们日常的许多编码 。