要了解其yield作用,必須瞭解什麼是 生成器。而且,瞭解生成器之前,必須瞭解 _iterables_。
可迭代: iterable
創建一個列表,自然是需要能一一閲讀其中每個元素。逐一讀取其項的過程被稱為迭代:
>>> 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,文件...
這些可迭代的方法很方便,因為您可以隨意讀取它們,但是您將所有值都存儲在內存中,當擁有很多值時,這並不總是想要的。
生成器:generator
生成器也是一種迭代器,一種特殊的迭代,特殊在只能迭代一次。生成器不會將所有值存儲在內存中,而是即時生成值:
generator: 發電機, 發生器,發電機發電但不儲能 ;)
>>> 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
yield關鍵字與return的使用方式一樣,不同之處在於該函數將返回生成器。
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # 創建一個 generator
>>> print(mygenerator) # mygenerator 是個對象!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
這個例子本身沒什麼用,但是當需要函數返回大量的值且只需要讀取一次時,使用 yield 就變得方便。
掌握yield,需要清楚的一點是:在調用函數時,在函數主體中編寫的代碼不會運行,該函數僅返回生成器對象,初學容易對這一點產生困惑。
其次要明白,代碼將在每次for使用生成器時從中斷處繼續。
現在最困難的部分是:
第一次for調用從您的函數創建的生成器對象時,它將從頭開始運行函數中的代碼,直到命中為止yield,然後它將返回循環的第一個值。然後,每個後續調用將運行您在函數中編寫的循環的下一次迭代,並返回下一個值。這將一直持續到生成器被認為是空的為止,這在函數運行時沒有命中時就會發生yield。那可能是因為循環已經結束,或者是因為您不再滿足"if/else"。
yield: 出產, 繳出, 讓出, 屈服, 讓路
用代碼來説明
生成器 generator:
# 創建返回生成器的方法
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
# 函數執行到這,被認為生成器空了
# 這裏不超過兩個值了: 左和右子對象
調用方 caller:
# 創建空的 list, 和一個包含當前對象引用的列表
result, candidates = list(), [self]
# 循環 candidates (開始只有一個元素)
while candidates:
# 取最後的 candidate 並從列表中移除
node = candidates.pop()
# 獲取 obj 與 candidate 間的距離
distance = node._get_dist(obj)
# 距離合適則填入結果
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# 並把 candidate 的子元素加入列表
# 循環直至鎖定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(): # 搞個銀行,弄點ATM提款機
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # 順利的話你要多少 ATM 給你多少
>>> 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 # 危機來了,不行了,沒錢了!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # 新的 ATM 也一樣
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # 危機過後 ATM 裏也沒錢
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # 搞個新的做新生
>>> 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__()方法)的過程。可迭代對象是可以從中獲取迭代器的任何對象。迭代器是使您可以迭代可迭代對象的對象。