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


当前回答

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

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

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

两者的输出结果相同。

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

其他回答

在描述如何使用发电机的许多伟大答案中, 我感到还没有给出一种答案。 这是编程语言理论的答案:

Python 中的收益率语句返回了一个发电机。 Python 的发电机功能返回了连续性( 具体地说, 是一种共同的常规, 但连续性代表了了解情况的一般机制 ) 。

编程语言理论的继续是更根本的计算方法,但通常不会被使用,因为它们极难解释,也很难执行。但是,关于继续的理念很简单:是计算状态尚未完成。在这种状态下,变量的当前值、尚未执行的操作等等被保存。然后,在程序稍后的某个时候,可以援引继续,使程序的变量被重新设置到状态,保存的操作被执行。

以这种更一般的形式, 延续可以用两种方式执行 。 以调用/ 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 编程中,我大量使用这种设计模式,要么是因为它可以节省我的代码线,要么更重要的是,在图形用户界面事件触发后管理控制流程。 )

这个职位的其余部分将不失为一般性,将连续性概念化为CPS, 因为它很容易理解和阅读。

现在让我们来谈谈Python 的发电机。 发电机是一种特定的子延续类型。 虽然继续一般能够保存计算状态( 即程序调用堆) , 但发电机只能保存循环器的循环状态 。 虽然这个定义对于发电机的某些使用案例来说有点误导 。 例如 :

def f():
  while True:
    yield 4

这显然是一个合理的可循环性,其行为是明确的 -- 每次发电机在发电机上转动时,它就会返回 4 (并永远这样做 ) 。但是,在考虑迭代器时,它可能并不是一种典型的可循环的类型(例如,收藏中的x:Do_hine(x) ) 。 这个例子说明了发电机的力量:如果有什么是迭代器,一个发电机可以保存其迭代状态。

需要重申: 继续可以保存程序堆叠的状态, 发电机可以保存循环状态 。 这意味着继续能力比发电机强大得多, 同时发电机也容易得多。 语言设计师更容易实施,程序设计员更容易使用( 如果您有时间燃烧, 试着读懂和理解关于继续和调用/ cc的页面 ) 。

但您可以很容易地实施(和概念化)发电机,作为延续传承风格的一个简单而具体的例子:

当调用产值时, 它会告诉函数返回一个延续。 当再次调用函数时, 它从它所剩的开始。 所以, 在伪假伪代码( 即不是伪代码, 但不是代码) 中, 生成器的下一个方法基本上如下 :

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

当产出关键字实际上为实际生成功能的合成糖时, 基本上是类似 :

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

记住这只是假码,而Python发电机的实际安装则更为复杂。 但是,为了了解正在发生的事情,试图使用持续的传记风格来实施生成器,而不使用产出关键字。

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

yield from <expr>

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

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

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

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

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

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

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

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

示例:

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

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

以下是基于收益率的简单方法, 用来计算Fibonacci系列, 解释如下:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

当你把这个输入你的REPL,然后尝试把它称为, 你会得到一个神秘的结果:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

这是因为向 Python 发出的产出信号 表明您想要创建一个生成器, 即一个根据需求产生价值的物体。

那么,你如何生成这些值?这要么直接通过下一个使用内置函数来实现,要么间接地通过将内置函数输入一个消耗值的构造来实现。

使用下个() 内置函数, 您可以直接引用. extext/ __ extext_ , 迫使生成器产生值 :

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

间接地,如果您为循环提供纤维、列表初始化器、图普特初始化器或其他任何期望产生/产生值的对象,您将“组装”生成器,直到它不再产生(并返回):

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

类似地,图普特首发器:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

生成器与功能不同, 因为它很懒。 它通过保持本地状态, 并允许您在需要的时候恢复运行来达到这个目的 。

当你喊叫它的时候,

f = fib()

Python 编译函数, 遇到产出关键字, 只需返回生成对象。 似乎没有什么帮助 。

当您要求它生成第一个值时, 它会直接或间接地执行它发现的所有语句, 直到它遇到一个产量, 然后它会返回您提供的产量和暂停值。 对于一个更能证明这一点的例子, 让我们使用一些打印电话( 如果在 Python 2 上用打印“ text ” 代替 打印“ text ” ):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

现在,输入REPL:

>>> gen = yielder("Hello, yield!")

您现在有一个生成对象, 正在等待一个命令来生成一个值。 使用下一个对象并查看打印的内容 :

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

未引用的结果是打印的内容。 引用的结果是从产出中返回的内容。 现在再次调用 :

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

生成器记得它被按产出值暂停, 然后从那里恢复。 下一则消息被打印, 并搜索收益声明以在它上再次暂停( 原因是同时循环 ) 。

产出 :

可以通过停止函数从函数返回一个值的多次。 您可以从它中返回一个值, 如从中产生 。 当返回大数据时, 将它分成小部分数据, 以防止大量使用内存 。

例如,下面的测试 () 可以通过停止测试( ) 逐个返回“ 1 ” 、 “ 2 ” 和 [ “ 3 ” 、 “ 四 ” 。 因此, 测试( ) 总共返回3倍, 总共返回3倍, 停止测试( ) 共返回3倍 :

def test():
    yield 'One'                  # Stop, return 'One' and resume 
    yield 'Two'                  # Stop, return 'Two' and resume
    yield from ['Three', 'Four'] # Stop and return ['Three', 'Four'] 

下面这三套代码可以调用测试() 并打印“ 1 ” 、 “ 2 ” 、 “ 三 ” 和 “ 四 ” :

for x in test():
    print(x)
x = test()
print(next(x))
print(next(x))
print(next(x))
print(next(x))
x = test()
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())

其结果是:

$ python yield_test.py
One
Two
Three
Four

此外,在利用回报和产出时,没有办法从回报中获得价值:

def test():
    yield 'One' 
    yield 'Two'
    yield from ['Three', 'Four']
    return 'Five' # 'Five' cannot be got

x = test()
print(next(x))
print(next(x))
print(next(x))
print(next(x))
print(next(x)) # Here

因此,在试图获取“ 五” 时, 下面有一个错误 :

$ python yield_test.py 
One
Two
Three
Four
Traceback (most recent call last):
  File "C:\Users\kai\yield_test.py", line 12, in <module>
    print(next(x))
          ^^^^^^^
StopIteration: Five