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 生成器与这个概念没有多大区别。 想象一下, 您有一个叫做条形码_ 生成器的函数, 可以为框生成独特的序列号 。 显然, 您可以在硬件( 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)

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

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

现实世界应用?

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

其他回答

TL; DR TR; TL; TDR

代替此:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

这样做:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

每当你发现自己从头开始编出一个清单时, 每一块都取而代之。

这是我第一次"啊哈"节奏节奏


收成是一种含糖的方式 说

构建一系列材料

相同行为 :

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

不同的行为 :

产量是单行道,只能绕过一次。当一个函数有收益时,我们称它为发电机功能。循环者就是它的回报。这些术语是明亮的。我们失去了一个容器的方便,但获得一系列按需要计算并任意延长的能量。

是懒惰的, 它会推卸计算。 函数中含有收益的函数在调用时不会实际执行。 它返回一个循环器对象, 记得它留下的位置。 每次您在调用循环器时( 这发生在换环) 执行步数向下一个产数前进。 返回会提高停止输出并结束序列( 这是换圈的自然结束 ) 。

产量是多功能的。数据不必全部储存在一起, 它可以一次提供一次。 它可以是无限的 。

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

如果您需要多个通行证,且系列不会太长,请在电话列表上填写:

>>> list(square_yield(4))
[0, 1, 4, 9]

英明地选择“产生”一词,因为这两个含义都适用:

产量——生产或供应(如农业)

...在系列中提供下一个数据

放弃或放弃(与政治权力一样)

...在传动器推进之前,将CPU执行。

要了解什么是产量,你必须了解什么是发电机。在你能够理解发电机之前,你必须了解易燃的发电机。

易变性

创建列表时,您可以逐项阅读其项目。逐项阅读其项目被称为迭代:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

My list 是可替换的。 当您使用列表理解时, 您会创建一个列表, 因而是一个可替换的 :

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

你可以使用的一切"... 在..."是一个可循环的; 列表,字符串,文件...

这些可替换的功能是实用的,因为您可以随心所欲地阅读,但您将所有值都存储在记忆中,当您拥有很多值时,这并不总是你想要的。

发电机发电机

发电机是迭代器, 一种可迭代的循环, 您只能循环一次 。 发电机不会存储记忆中的所有值, 它们会在苍蝇上生成值 :

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

除了使用()而不是使用()之外,它是一样的。但是,由于发电机只能使用一次,所以不能在我的生成器中为我第二次执行,因为发电机只能使用一次:它们计算0,然后忘记它,然后计算1,然后结束计算4,一个一个一个地计算。

产量d

函数将返回一个生成器。

>>> def create_generator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

这是一个毫无用处的例子, 但当你知道你的功能会返回 一大堆的值时, 它就方便了, 你只需要读一次。

要掌握输出能力, 您必须明白当您调用函数时, 您在函数体中写入的代码没有运行。 函数只返回生成对象, 这有点棘手 。

然后,你的代码会继续 从它离开的每一次 使用发电机。

现在,硬的部分:

第一次调用您函数所创建的生成器对象时, 它会运行您函数的代码, 从开始到它产生, 然后返回循环的第一个值。 然后, 以后每次调用都会运行您在函数中写入的循环的再次迭代, 然后返回下一个值。 这将一直持续到生成器被认为是空的, 当函数运行时不会打出收益。 这可能是因为循环结束, 或者因为您不再满足“ if/ else ” 。


您的代码解释

发电机:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if the distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if the distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there are no more than two values: the left and the right children

调用者 :

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If the distance is ok, then you can fill in the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate to the candidate's list
    # so the loop will keep running until it has looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

本代码包含几个智能部分 :

在列表中循环迭代, 但列表会随着循环迭代而扩展。 这是一个简单的方式来查看所有这些嵌套的数据, 即使它是一个有点危险的, 因为您可以以无限环结束。 在此情况下, 候选人 。 extendend( rode._ get_ child_ camedates( root, min_ dist, max_ distist)) 将耗尽所有生成器的值, 但同时继续创建新生成的生成对象, 这些对象将产生与先前的相异的值, 因为它不会被应用到同一个节点上 。 扩展 () 方法是一种列表对象方法, 期待一个可重复的列表对象方法, 并将其添加到列表中 。

通常,我们向它传递一份清单:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

但在你的代码中,它有一个发电机, 这是很好的,因为:

你不需要两次阅读这些值。 你可能有很多孩子, 你不想把他们都保存在记忆中。

之所以有效,是因为 Python 并不在意一种方法的论据是否是一个列表。 Python 期望它能用字符串、列表、图普勒和生成器来操作。 这叫做鸭字打字, 也是Python之所以如此酷的原因之一。 但是这是另一个故事, 另一个问题...

您可以在这里停下来,或者读一下,看一个生成器的先进使用:

控制发电机耗竭

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

注: Python 3, 使用打印( corner_street_atm._next___ ()) 或打印( ext( corner_ street_ atm) )

它可以对控制获取资源等各种事情有用。

义大便,你最好的朋友

Itertool 模块包含操作可替换文件的特殊功能 。 是否想要复制一个生成器? 连锁二生成器? 组值在单行的嵌套列表中? 地图/ Zip 不创建另一个列表 ?

然后就进口它的工具。

举个例子,让我们看看四匹马赛的到货订单

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

了解迭代的内部机制

迭代是一个过程, 意味着可迭代( 实施 _ etre_ () 方法) 和迭代( 实施 ext_ () 方法) 。 迭代是您可以从中获取迭代器的任何对象。 迭代器是允许您循环到可迭代的物体 。

本文中有更多关于环环如何运作的论述。

发电机可以使个别经过处理的物品立即得到处理(不必等待整个收集过程的处理),下面的例子说明了这一点。

import time

def get_gen():
    for i in range(10):
        yield i
        time.sleep(1)

def get_list():
    ret = []
    for i in range(10):
        ret.append(i)
        time.sleep(1)
    return ret


start_time = time.time()
print('get_gen iteration (individual results come immediately)')
for i in get_gen():
    print(f'result arrived after: {time.time() - start_time:.0f} seconds')
print()

start_time = time.time()
print('get_list iteration (results come all at once)') 
for i in get_list():
    print(f'result arrived after: {time.time() - start_time:.0f} seconds')

get_gen iteration (individual results come immediately)
result arrived after: 0 seconds
result arrived after: 1 seconds
result arrived after: 2 seconds
result arrived after: 3 seconds
result arrived after: 4 seconds
result arrived after: 5 seconds
result arrived after: 6 seconds
result arrived after: 7 seconds
result arrived after: 8 seconds
result arrived after: 9 seconds

get_list iteration (results come all at once)
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds

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

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

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

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

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

尝试获得一份有回报的 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> 。

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

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