“收益”关键字有什么作用?

2022-09-05 00:40:52

关键字在Python中的用途是什么?它有什么作用?yield

例如,我试图理解这个代码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_candidates


1. 这段代码是由 Jochen Schulz (jrschulz) 编写的,他为度量空间创建了一个很棒的 Python 库。这是指向完整源代码的链接:模块 mspace

答案 1

要了解什么是发电机,您必须了解发电机是什么。在你理解生成器之前,你必须了解可迭代对象yield

可迭代

创建列表时,可以逐个读取其项目。逐个读取其项目称为迭代:

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

mylist可迭代的。使用列表推导式时,将创建一个列表,因此可迭代:

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

所有你可以使用 “” 的东西都是可迭代的; , , 文件...for... in...listsstrings

这些可迭代对象非常方便,因为您可以根据需要读取它们,但是您可以将所有值存储在内存中,并且当您有很多值时,这并不总是您想要的。

发电机

生成器是迭代器,一种只能迭代一次的可迭代器。生成器不会将所有值存储在内存中,它们会动态生成值

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

它只是一样的,除了你用代替了。但是,您不能执行第二次,因为生成器只能使用一次:它们计算0,然后忘记它并计算1,然后结束计算4,一个接一个。()[]for i in mygenerator

屈服

yield是一个关键字,用法类似 ,只是函数将返回一个生成器。return

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

这是一个无用的示例,但是当您知道您的函数将返回一组巨大的值时,它非常方便,您只需要读取一次。

要掌握,您必须了解,当您调用函数时,您在函数体中编写的代码不会运行。该函数仅返回生成器对象,这有点棘手。yield

然后,每次使用生成器时,您的代码都将从中断的位置继续。for

现在困难的部分:

第一次调用从函数创建的生成器对象时,它将从头开始运行函数中的代码,直到它命中,然后它将返回循环的第一个值。然后,每个后续调用将运行您在函数中编写的循环的另一次迭代,并返回下一个值。这将一直持续到生成器被视为空,当函数运行而不命中 时会发生这种情况。这可能是因为循环已经结束,或者因为您不再满足 .foryieldyield"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 is 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 distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidate's list
    # so the loop will keep running until it will have 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

此代码包含几个智能部件:

  • 循环循环访问列表,但在循环迭代时列表会扩展。这是一种简明扼要的方式来遍历所有这些嵌套数据,即使它有点危险,因为你可能会得到一个无限循环。在这种情况下,请穷尽生成器的所有值,但不断创建新的生成器对象,这些对象将生成与以前的值不同的值,因为它不应用于同一节点。candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))while

  • 该方法是一个列表对象方法,它需要可迭代项,并将其值添加到列表中。extend()

通常我们会向它传递一个列表:

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

但是在你的代码中,它得到了一个生成器,这很好,因为:

  1. 您无需将值读取两次。
  2. 你可能有很多孩子,你不希望他们都存储在内存中。

它之所以有效,是因为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,使用或print(corner_street_atm.__next__())print(next(corner_street_atm))

它可用于各种操作,例如控制对资源的访问。

Itertools,你最好的朋友

迭代工具模块包含操作可迭代对象的特殊函数。曾经希望复制生成器吗?链上两台发电机?使用单行对嵌套列表中的值进行分组? 而不创建另一个列表?Map / Zip

然后只是.import itertools

举个例子?让我们看看四马比赛的可能到达顺序:

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

了解迭代的内部机制

迭代是一个暗示可迭代(实现方法)和迭代器(实现方法)的过程。可迭代对象是您可以从中获取迭代器的任何对象。迭代器是允许您迭代可迭代的对象。__iter__()__next__()

本文中有更多关于 for 循环如何工作的信息


答案 2

理解的捷径yield

当您看到带有语句的函数时,请应用这个简单的技巧来理解将会发生什么:yield

  1. 在函数的开头插入一行。result = []
  2. 将每个替换为 。yield exprresult.append(expr)
  3. 在函数底部插入一行。return result
  4. 耶 - 没有更多的声明!阅读并找出代码。yield
  5. 将函数与原始定义进行比较。

这个技巧可能会让你了解函数背后的逻辑,但实际发生的事情与基于列表的方法中发生的事情有很大不同。在许多情况下,yield 方法的内存效率更高,速度更快。在其他情况下,这个技巧会让你陷入无限循环,即使原始函数工作得很好。继续阅读以了解更多信息...yield

不要混淆迭代器、迭代器和生成器

首先,迭代器协议 - 当你写

for x in mylist:
    ...loop body...

Python 执行以下两个步骤:

  1. 获取 的迭代器:mylist

    调用 ->这将返回一个带有方法的对象(或在 Python 3 中)。iter(mylist)next()__next__()

    [这是大多数人忘记告诉你的步骤]

  2. 使用迭代器循环访问项目:

    继续调用从步骤 1 返回的迭代器上的方法。将 的返回值赋给并执行循环体。如果从 内部引发异常,则意味着迭代器中没有更多的值,并且循环被退出。next()next()xStopIterationnext()

事实是,Python在想要遍历对象内容时都会执行上述两个步骤 - 因此它可能是for循环,但它也可以是类似代码的(其中是Python列表)。otherlist.extend(mylist)otherlist

这是一个可迭代的,因为它实现了迭代器协议。在用户定义的类中,可以实现该方法以使类的实例可迭代。此方法应返回迭代器。迭代器是具有方法的对象。可以在同一类上实现这两个类,并返回。这适用于简单情况,但当您希望两个迭代器同时循环访问同一对象时,则不行。mylist__iter__()next()__iter__()next()__iter__()self

这就是迭代器协议,许多对象实现了这个协议:

  1. 内置列表、字典、元组、集、文件。
  2. 实现 的用户定义类。__iter__()
  3. 发电机。

请注意,循环不知道它正在处理哪种对象 - 它只是遵循迭代器协议,并且很乐意在调用时获取一个又一个项目。内置列表逐个返回其项目,字典逐个返回,文件逐个返回,依此类推。发电机返回...好吧,这就是进来的地方:fornext()yield

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

而不是语句,如果你在三个语句中,只有第一个语句将被执行,并且函数将退出。但不是普通的功能。调用 时,它不会返回 yield 语句中的任何值!它返回一个生成器对象。此外,该函数不会真正退出 - 它进入挂起状态。当循环尝试循环访问生成器对象时,函数将从其挂起状态恢复到其挂起状态,在它之前返回的下一行之后,执行下一行代码(在本例中为一个语句),并将其作为下一项返回。这种情况一直持续到函数退出,此时生成器启动 ,并且循环退出。yieldreturnf123()f123()f123()foryieldyieldStopIteration

因此,生成器对象有点像适配器 - 在一端,它通过公开和方法来保持循环快乐,从而展示迭代器协议。但是,在另一端,它运行函数刚好足以从中获取下一个值,并将其放回挂起模式。__iter__()next()for

为什么使用发电机?

通常,您可以编写不使用生成器但实现相同逻辑的代码。一种选择是使用我之前提到的临时列表“技巧”。这并非在所有情况下都有效,例如,如果您有无限循环,或者当您有一个非常长的列表时,它可能会有效地使用内存。另一种方法是实现一个新的可迭代类SomethingIter,该类将状态保留在实例成员中,并在其(或Python 3)方法中执行下一个逻辑步骤。根据逻辑,方法内部的代码最终可能看起来非常复杂,并且容易出错。在这里,发电机提供了一个干净和简单的解决方案。next()__next__()next()