要了解其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
,文件...
这些可迭代的方法很方便,因为您可以随意读取它们,但是您将所有值都存储在内存中,当拥有很多值时,这并不总是想要的。
发电机
生成器是迭代器,一种迭代,您只能迭代一次。生成器不会将所有值存储在内存中,它们会即时生成值:
>>> 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 createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
这是一个无用的示例,但是当您知道函数将返回大量的值(只需要读取一次)时,它就很方便。
要掌握yield
,您必须了解在调用函数时,在函数主体中编写的代码不会运行。该函数仅返回生成器对象,这有点棘手:-)
然后,您的代码将在每次for
使用生成器时从中断处继续。
现在最困难的部分是:
第一次for
调用从您的函数创建的生成器对象时,它将从头开始运行函数中的代码,直到命中为止yield
,然后它将返回循环的第一个值。然后,每个后续调用将运行您在函数中编写的循环的另一个迭代,并返回下一个值。这将一直持续到生成器被认为是空的为止,这在函数运行时没有命中时就会发生yield
。那可能是因为循环已经结束,或者是因为您不再满足"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期望可迭代,因此它将与字符串,列表,元组和生成器一起使用!这就是所谓的鸭子输入,这是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,您最好的朋友
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
工作的更多信息。
yield
这个答案并不神奇。当您yield
在任何地方调用包含语句的函数时,都会得到一个生成器对象,但是没有代码运行。然后,每次您从生成器中提取一个对象时,Python都会在函数中执行代码,直到涉及到yield
语句为止,然后暂停并交付该对象。当您提取另一个对象时,Python会在yield
和之后继续执行,直到到达另一个对象为止yield
(通常是相同的对象,但是要经过一个迭代)。这一直持续到该功能结束时为止,此时发电机被认为已耗尽。