翻了一篇workflow上關於yield的用法,翻的有點爛,在這裏貽笑大方了,慢慢來,總是期待着一點一點的進步。
爲了理解yield的機制,我們需要理解什麼是生成器。在此之前先介紹迭代器iterables。
Iterables
當你創建一個list,你可以一個一個的獲取,這種列表就稱爲迭代:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
Mylist 是一個迭代器. 當你理解它爲一個list,它便是可迭代的:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
任何可以用 for in 來迭代讀取的都是迭代容器,例如lists,strings,files.這些迭代器非常的便利,因爲你可以想取多少便取多少,但是你得存儲所有的值,其中很多值都完全沒有必要每次都保持在內存中。
Generators
Generators(生成器)也是可迭代的,但是你每次只能迭代它們一次,因爲不是所有的迭代器都被一直存儲在內存中的,他們臨時產生這些值:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
生成器幾乎和迭代器是相同的,除了符號[]變爲()。但是你無法用兩次,因爲他們只生成一次:他們生成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
上面的例子幾乎非常積累,但是它很好的闡釋了yield的用法,我們可以知道createGenerator()生成的是一個生成器。
爲了掌握yield的精髓,你一定要理解它的要點:當你調用這個函數的時候,你寫在這個函數中的代碼並沒有真正的運行。這個函數僅僅只是返回一個生成器對象。有點過於奇技淫巧:-)
然後,你的代碼會在每次for使用生成器的時候run起來。
現在是解釋最難的地方:
當你的for第一次調用函數的時候,它生成一個生成器,並且在你的函數中運行該循環,知道它生成第一個值。然後每次調用都會運行循環並且返回下一個值,知道沒有值返回爲止。該生成器背認爲是空的一旦該函數運行但是不再刀刀yield。之所以如此是因爲該循環已經到達終點,或者是因爲你再也不滿足“if/else”的條件。
Your code explained
# 這裏你創建一個node對象的一個生成器生成方法Here you create the method of the node object that will return the generator
def node._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]
# 循環candidates列表,只有一個元素。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
這段代碼包含一些非常機智的部分:
1. list的循環迭代部分,但是list在循環的同時又在拓展,:)這種方法是一種循環內嵌式的數據的相對簡潔的方法,但是又存在着一些風險可能會導致死循環的情況。在這個例子當中,candidates.extend(node._get_child_candidates(distance,
min_dist, max_dist))
耗盡所有的的生成器的值,但是當保持生成新的生成器對象,並且依據之前生成器產生許多不同的值,由於它產生於不同的節點。
2. extend()方法是一個list 對象方法,它產生一個迭代器並且添加它的值到list當中去。
通常我們
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
但是代碼中獲得一個生成器,這種方式比較好的原因如下:
首先是你無須讀取該值兩次。
然後你不需要把所有的值都放在內存中。
與此同時,它能夠owrk的原因是python不關心一個方法的參數石佛是一個list.期待是一個迭代器所以它能夠適用於strings,lists,tuples以及生成器。這被稱爲動態類型或者鴨子類型(duck typing)是python 如此酷的一大原因。鴨子類型又是另外一個問題了,blablabla。
現在讓我們來看看一些高級的用法:
控制生成器資源消耗:
>>> 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 # 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模塊包含一些特別的函數去執行迭代器。有沒有想過去複製一個生成器 或者鏈接兩個生成器?等等。
引入itertools就好了,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)]
最後是理解迭代器的內部機制:
Iteration is a process implying iterables (implementing the __iter__()
method)
and iterators (implementing the __next__()
method).
Iterables are any objects you can get an iterator from. Iterators are objects that let you iterate on iterables.
更多的相關內容可以閱讀 循環如何工作.