Python中`yield`关键字有什么用?
以下文章摘自代码领悟
提问
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
回答
要了解yield的作用,您必须了解生成器是什么。 在了解生成器之前,您必须了解iterables。
迭代器
创建列表时,您可以逐个读取其项目。 逐个读取其项称为迭代:
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..."的所有内容都是可迭代的;lists,strings,文件。..
这些iterables很方便,因为您可以随心所欲地读取它们,但是您将所有值存储在内存中,当您有很多值时,这并不总是您想要的。
发电机
生成器是迭代器,一种可迭代的你只能迭代一次。 生成器不会将所有值存储在内存中,它们会动态生成值:
mygenerator = (x*x for x in range(3))
for i in mygenerator:
... print(i)
0
1
4
除了你使用()而不是[]之外,它是一样的。 但是,您不能第二次执行for i in mygenerator,因为生成器只能使用一次:它们计算0,然后忘记它并计算1,并结束计算4,一个接一个。
产量
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使用生成器时停止的位置继续。
现在最难的部分:
当for第一次调用从你的函数创建的生成器对象时,它将从一开始就在你的函数中运行代码,直到它点击yield,然后它将返回循环的第一个值。 然后,每个后续调用都将运行您在函数中编写的循环的另一个迭代,并返回下一个值。 这将继续下去,直到生成器被认为是空的,这发生在函数运行而没有击中yield时。 这可能是因为循环已经结束,或者因为你不再满足an"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]
但是在你的代码中,它得到了一个生成器,这很好,因为:
您不需要读取两次值。
你可能有很多孩子,你不希望他们都存储在内存中。
它的工作原理是因为Python不关心方法的参数是否是列表。 Python需要迭代器,因此它将与字符串、列表、元组和生成器一起工作! 这被称为duck typing,也是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
...
注意:对于Python3,使用print(corner_street_atm.next())或print(next(corner_street_atm))
它对于控制对资源的访问等各种事情都很有用。
Itertools,你最好的朋友
itertools模块包含用于操作iterables的特殊函数。 曾经想复制一个发电机吗? 链两个发电机? 在一个单行嵌套列表中分组值? 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)]
理解迭代的内在机制
迭代是一个隐含iterables(实现__iter__()方法)和iterators(实现__next__()方法)的过程。 Iterables是您可以从中获取迭代器的任何对象。 迭代器是允许您迭代可迭代对象的对象。
在这篇文章中有更多关于for循环如何工作的内容。

浙公网安备 33010602011771号