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 的发电机功能返回了连续性( 具体地说, 是一种共同的常规, 但连续性代表了了解情况的一般机制 ) 。

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

以这种更一般的形式, 延续可以用两种方式执行 。 以调用/ 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发电机的实际安装则更为复杂。 但是,为了了解正在发生的事情,试图使用持续的传记风格来实施生成器,而不使用产出关键字。

其他回答

以下是一个简单的例子:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

产出:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

我不是Python开发者,但对我来说,它似乎保持了程序流程的位置,而下一个循环则从“当量”的位置开始。 它似乎正在等待着这个位置,就在那个位置之前,它正在向外回报一个价值,而下一次将继续工作。

这似乎是一个有趣和好的能力:

理解产出的快捷键

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

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

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

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

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

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 ) 方法中执行下一个逻辑步骤 。 根据逻辑, 下一个( ) 方法中的代码可能最终会查找非常复杂和易被错误的代码 。 在这里, 生成器可以提供一个简单明了的解决方案 。

- 功能 - 返回。

发电机 -- -- 产量(含有一个或多个产量和零或更多回报率)。

names = ['Sam', 'Sarah', 'Thomas', 'James']


# Using function
def greet(name) :
    return f'Hi, my name is {name}.'
    
for each_name in names:
    print(greet(each_name))

# Output:   
>>>Hi, my name is Sam.
>>>Hi, my name is Sarah.
>>>Hi, my name is Thomas.
>>>Hi, my name is James.


# using generator
def greetings(names) :
    for each_name in names:
        yield f'Hi, my name is {each_name}.'
 
for greet_name in greetings(names):
    print (greet_name)

# Output:    
>>>Hi, my name is Sam.
>>>Hi, my name is Sarah.
>>>Hi, my name is Thomas.
>>>Hi, my name is James.

发电机看起来像一个函数,但行为举止却像一个迭代器。

发件人继续从它所在的位置执行 。 恢复后, 函数在最后产值运行后立即继续执行 。 这允许它的代码在一段时间内生成一系列的值, 代之以它们一次性计算全部值, 然后把它们像列表一样送回去 。

def function():
    yield 1 # return this first
    yield 2 # start continue from here (yield don't execute above code once executed)
    yield 3 # give this at last (yield don't execute above code once executed)

for processed_data in function(): 
    print(processed_data)
    
#Output:

>>>1
>>>2
>>>3

注:放弃不应在尝试中.最终建造。

这是关于产量的心理形象。

我想把一条线视为有堆叠(即使它不是用这种方式执行的)。

当调用一个普通函数时, 它会将其本地变量放入堆栈, 进行一些计算, 然后清除堆栈和返回。 其本地变量的值再也不会被看到 。

当它的代码开始运行时( 即函数被调用后, 返回一个生成对象, 其下一个( ) 方法随后被引用) , 它同样将其本地变量放在堆叠上, 并进行一段时间的计算 。 但是, 当它点击收益语句时, 在清理堆叠中的一部分并返回之前, 它会对其本地变量进行截图, 并将其存储在生成对象中 。 它还写下它目前位于代码中的位置( 即特定收益语句 ) 。

所以这是一种冷冻功能 发电机挂在了上面

当下一个 () 后被调用时, 它会从堆栈上取回函数的物品, 并重新激活它。 函数继续从剩余部分进行计算, 无视它刚刚在冷藏室里度过了一个永恒的时间 。

比较以下实例:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

当我们称第二个函数为第二个函数时, 它的行为与第一个函数非常不同。 收益声明可能无法达到, 但是如果它存在任何地方, 它会改变我们正在处理的东西的性质 。

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

调用 powerFunction () 不运行它的代码, 而是让一个生成器出自代码 。 (也许用 powerFunction () 的前缀命名这种东西作为可读性是一个好主意 。)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

gi_ code 和 gi_ frame 字段是存放冻结状态的地方。 使用 dir (.) 来探索它们, 我们可以确认上面的心理模型是可信的 。

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

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

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