# Python yield用法浅析(stackoverflow)

grisse

## 问题

Python中`yield`关键字的用途是什么？它有什么作用？

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

&sup1 ：代码来自 Jochen Schulz (jrschulz), who made a great Python library for metric spaces. 这是完整源代码的链接：Module mspace.

## 回答

### 可迭代对象 (iterables)

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

`mylist`就是一个可迭代对象(iterable)。当你使用列表生成式(list comprehension)创建一个列表(list)，即创建了一个可迭代对象。

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

### 生成器 (Generators)

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

### yield

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

`for`第一次调用从函数创建的生成器对象，函数将从头开始执行直到遇到`yeild`，然后返回`yield`后的值作为第一次迭代的返回值。接下来每次调用都会再次执行你在函数中定义的循环，并返回(return)下一个值，直到没有值可以返回(return)。

``````# 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 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 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 candidates 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()`是一个列表(list)对象的方法，作用于可迭代对象(iterable)，并将其值添加到列表里。

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

1. 你不必重复读取这些值
2. 你可以有很多子对象，但不需要将它们都存储在内存里。

### 控制生成器的穷尽 (Controlling a generator exhaustion)

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

### Itertools，你最好的朋友 (Itertools, your best friend)

itertools模块包含很多处理可迭代对象的特殊方法。曾经想要复制一个生成器吗？连接两个生成器？用一行代码将嵌套列表中的值进行分组？不创建另一个列表进行`Map/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)]``````

42 声望
3 粉丝
0 条评论