博客 / 詳情

返回

yield 關鍵字在 Python 中的用途是什麼?

要了解其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... 的數據結構都是可迭代的;listsstrings,文件...

這些可迭代的方法很方便,因為您可以隨意讀取它們,但是您將所有值都存儲在內存中,當擁有很多值時,這並不總是想要的。

生成器: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] 

但是在上面的代碼中,得到了一個生成器,這更好,因為:

  1. 不需要兩次讀取值。
  2. 可能有很多子元素,並且不希望所有子元素都存儲在內存中。

這樣做之所以有效,是因為 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__()方法)的過程。可迭代對象是可以從中獲取迭代器的任何對象。迭代器是使您可以迭代可迭代對象的對象。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.