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


当前回答

所有的答案都是伟大的, 但对于新人来说有点困难。

我猜你已经得知回程声明了

作为类比,回归和收益是双胞胎。 回归意味着“ 回归和停止 ” , 而“ 回归”则意味着“回归,但继续 ” 。

尝试获得一份有回报的 num_ 列表 。

def num_list(n):
    for i in range(n):
        return i

运行它:

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

你看,你只得到一个数字,而不是一个他们的名单。返回永远不允许你快乐地获胜,只要执行一次就退出。

产生结果

将返回替换为产出 :

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]

现在,你赢得了所有的数字。

与一次运行和停止的返回相比, 一次运行和一次运行, 一次运行和一次运行。 您可以将返回解释为一个返回, 一次返回作为全部返回。 这叫“ 易动 ” 。

再多走一步,我们就可以重新写出回报的收益声明

In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

这是关于产量的核心。

列表返回输出与目标产出的区别是:

您总是可以从列表对象中获取 [0, 1, 2] , 但只能从“ 对象输出输出” 中提取一次 。 因此, 它有一个新的名称生成对象, 如 Out[ 11] 所示 : <generator 对象 num_ list at 0x10327c990> 。

最后,作为格罗克语的比喻:

双胞胎名单和发电机是双胞胎

其他回答

还有一件事情要提: 产量的函数其实不一定要终止。我写了这样的代码:

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

这样我就可以用在别的代码里了

for f in fib():
    if some_condition: break
    coolfuncs(f);

它确实有助于简化一些问题,使一些事情更容易处理。

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

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

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

两者的输出结果相同。

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

python 的输出与返回语句类似,但有些差异除外。如果要从函数返回多个值,返回语句将把所有值都作为列表返回,并将其存储在调用符块的内存中。但如果我们不想使用额外的内存,会怎样?相反,我们需要在需要时从函数中获取该值。这是产出的来源。考虑以下函数:

def fun():
   yield 1
   yield 2
   yield 3

打电话的人是:

def caller():
   print ('First value printing')
   print (fun())
   print ('Second value printing')
   print (fun())
   print ('Third value printing')
   print (fun())

上述代码段(调用函数),如果调用,产出:-

First value printing
1
Second value printing
2
Third value printing
3

从上文可以看出, 产出返回其调用器的值, 但当函数再次调用时, 它不会从第一个语句开始, 而是从产出后右侧的语句开始。 在上述示例中, “ 第一值打印” 打印, 函数被调用。 1 被回传并打印。 然后, 打印“ 第二值打印” , 并再次调用有趣 () 。 它不打印 1 (第一个语句) , 而是返回 2 , 也就是说, 仅从产出 1 之后的语句 。 同样的程序会进一步重复 。

想象一下, 你创造了一个非凡的机器, 能够每天生成成千上万个灯泡。 机器用一个独特的序列号的盒子生成这些灯泡。 您没有足够的空间同时存储所有这些灯泡, 所以您想要调整它来生成点燃灯泡 。

Python 生成器与这个概念没有多大区别。 想象一下, 您有一个叫做条形码_ 生成器的函数, 可以为框生成独特的序列号 。 显然, 您可以在硬件( RAM) 的限制下, 由函数返回大量这样的条形码 。 一个更明智和空间效率更高的选项是按需生成这些序列号 。

机器代码 :

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

注意下个( 条码) 位 。

如你所见, 我们有一个自足的“ 功能” , 每次生成下一个独特的序列号。 此函数返回一个生成器 。 正如您所看到的, 我们并不是每次我们需要一个新的序列号时都会调用该功能, 而是使用下一个( ) , 给生成器来获取下一个序列号 。

低拉隔热器

更精确地说, 这个生成器是一个懒惰的循环器 。 循环器是一个帮助我们绕过一个天体序列的物体。 它被称为懒惰, 因为它在需要之前不会在内存中装入序列中的所有项目。 上一个示例中的下一个是从循环器获取下一个项目的清晰方式 。 隐含方式用于循环 :

for barcode in barcode_generator():
    print(barcode)

这将无穷尽地打印条形码, 但你不会失去内存 。

换句话说,一个发电机看起来像一个函数,但行为却像一个迭代器。

现实世界应用?

最后, 真实世界应用程序 。 当您在大序列中工作时, 它们通常是有用的 。 想象一下从有数十亿记录的磁盘上读取一个巨大的文件。 在您能够处理其内容之前, 在记忆中读取整个文件, 很可能是行不通的( 也就是说, 您将失去记忆 ) 。

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

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

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

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