·python·用產生器和迭代器實現自己的xrange聲明:本文由戀花蝶發表於http://blog.csdn.net/lanphaday,著作權,歡迎轉載。轉載時應保留聲明。謝謝。 用過python的朋友一定很熟悉下面這兩行代碼:>>> for i in xrange(0,10,1): print i上面的兩行代碼是用一個迴圈列印0-9這十個數字。你也想實現像xrange這樣的可以用在for語句裡的函數(類)嗎?那跟我來吧! 首先來介紹一下python的yield語句,Yield這個單詞本身有產生、產出的意思,它的文法是:yield 運算式關於yield語句,官方manual是這樣說的:yield語句僅用以定義產生器函數,而且它只能出現在產生器函數內;在函數定義中使用yield語句的充分理由是想實現以個產生器函數而不是普通函數。當產生器函數被調用,它返回一個視作產生器的迭代器的迭代器、更通俗地說是一個產生器。產生器函數的函數體將被產生器的next方法重複調用直到產生一個異常;當yield語句被執行的時候產生器的狀態被凍結並且運算式的值返回給next()的調用者,所謂“凍結”我們可以理解成函數在這裡被儲存現場並切換了出去(如果你瞭解作業系統的進程管理的話,應該很容易理解這句話)。 嗯,太隱晦了些,看個執行個體吧。>>> def simple_xrange (num): while(num): yield num num -= 1 >>> l = list(simple_xrange(8))>>>print l[8, 7, 6, 5, 4, 3, 2, 1]在上例中我們實現了一個簡單的xrange,產生倒序的數字系列。但還是看不出這個simple_xrange是怎麼執行的,現在我們來看看下面的實驗:>>> it = simple_xrange (8)>>> it.next()8>>> it.next()7>>> it.next()6……>>> it.next()1>>> it.next() Traceback (most recent call last): File "<pyshell#48>", line 1, in -toplevel- it.next()StopIteration現在我們從上面的實驗中來看simple_xrange的執行過程:1、 當執行it = simple_xrange(8)時,simple_xrange返回一個產生器,即it成為一個產生器。2、 當執行it.next()時,simple_xrange的函數體被執行,當執行到yield num語句時,simple_xrange被“凍結”,然後返回num,即83、 再次執行it.next(),simple_xrange“解凍”,執行num -= 1,因為是迴圈,所以再執行while(num),這時又是執行yield num,simple_xrange被“凍結”,返回num,即74、 再一次次調用下去,直到simple_xrange的while(num)不成立,跳出迴圈,返回時next()函數拋出一個StopIteration異常,這時產生器函數就執行完結了。把上面的1234條目跟上文python manual的說法對照一下,是相互呼應的,這樣我們就理解了xrange的實現機理,從而可以利用yield語句寫出自己的xrange了。 理解了yield之後,理解另一種實現xrange的方法就容易多了,這種方法就是定義自己的迭代器。對於迭代器,python manual的說法是這樣的:python支援一種超越容器的迭代器觀念,使得使用者定義的類支援迭代。迭代器對象需要支援__iter__()和next()兩個方法,其中__iter__()返回迭代器自身,next()返回系列的下一個元素。嗯,還是通過執行個體來說吧:>>> class simple_xrange: def __init__(self, num): self._num = num def __iter__(self): return self def next(self): if self._num <= 0: raise StopIteration tmp = self._num self._num -= 1 return tmp >>> l = list(simple_xrange(8))>>> l[8, 7, 6, 5, 4, 3, 2, 1]哈哈,讀一下原始碼,似乎這個比yield語句更簡明易懂,也許這就是在有了yield語句之後還要支援迭代器類型的原因吧!有了yield知識,理解這段原始碼是很簡單的了,我就不多言了。 搞了這麼久,實現自己的xrange有必要嗎?當然是有的,xrange只是產生了一個系列,如果要對這個系列有什麼擴充的話,寫出來的代碼就比較難看了。舉個在現實工作中我遇到的例子:我做一個紙牌遊戲,我用list來表示將要打出的牌(我用0~53表示一副牌,其中0表示最小的牌——方塊3),如[0,0,3,3]表示兩對編號分別為0,3的牌,即由兩個方塊3兩個黑桃3組成的炸彈(本遊戲使用兩副牌,所以可以有兩個相同的牌ID)。後來修改了遊戲規則,新的遊戲規則規定大joker(牌ID為53)可以變化為任意牌,比如[0,0,3,53]也是一個炸彈。這時我寫了下面的代碼來判斷一個list是不是一個正確的牌型:#當cards裡有big joker時調用本函數判斷是否為有效牌型def is_valid_pattern_with_big_joker(cards): _cards = cards[:] #因為要改變cards,所以函數內使用cards的拷貝 be_replace_card = 53 #big joker將被替換 #枚舉所有的可能 for i in xrange(53, -1,-1): _cards.remove(be_replace_card) _cards.append(i) be_replace_card = i if is_valid_pattern(_cards)#如果還有big joker,is_valid_pattern會遞迴調用本函數 return True return False看那for迴圈的迴圈體,多麼複雜,又是remove又是append還有中間變數要儲存,有沒有辦法簡單點?有!使用迭代器吧。class ReplacedBigJokerCards: def __init__(self, cards): self._cards = cards[:] self._be_replaced_card = 53 self._candidate = 52 def __iter__(self): return self def next(self): if self._candidate < 0: raise StopIterationself._cards.remove(self._be_replace_card)self._cards.append(self._candidate)self._be_replace_card = self._candidate self._candidate -= 1 return self._cards def is_valid_pattern_with_big_joker(cards): for _cards in ReplacedBigJokerCards(cards): if is_valid_pattern(_cards) return True return False 看,現在把大joker變牌的細節隱藏起來,is_valid_pattern_with_big_joker變得優雅多了。重要的另一點就是在遊戲中,除了判定牌型外,還有智能提示等多個功能都能夠重用ReplaceBigJokerCards,使用這樣的定製迭代器,比散落在代碼各處的remove/append比好得多。