翻譯|Stack Overflow上關於Python的高票問答(一)

Stackoverflow是一個非常優秀的與程序相關的IT技術問答的英文網站。無論你處在什麼狀態,當你編程碰到問題的時候你總能找到你想要的答案。學習編程專欄打算翻譯這一系列的問答計划進行的方向如下。

翻譯Stack Overflow上關於Python的高票問答(Java)(JavaScript)(Php)(C#)每一種語言都會出兩到三篇文章,每一篇會有一到三個問題。

感謝朋友@撓米 在翻譯過程中給出的幫助。

問題鏈接: python - What does the "yield" keyword do?

問題描述:

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 = list(), [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

我的問題是:當_get_child_candidates這個方法被調用的時候,到底發生了什麼?是返回一個list,還是返回一個單個元素?後續的調用將在什麼時候終止?

[1]上述代碼來自 Jochen Schulz (jrschulz),他做了一個度量空間的Python庫。以下是代碼的完整鏈接:Module mspace.

要理解yield是什麼,它幹什麼。首先你要了解可迭代對象(Iterable),然後再去了解生成器(generators),最後你才能了解yield。

當你定義了一個list之後,你可以一個一個的讀取其中的元素。遍歷list中的元素這個動作我們就把它稱做:迭代(Iteration)。

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

以上的 mylist是一個可迭代對象(Iterable),當你使用列表推導式(list comprehension)創建一個list,相當於創建了一個可迭代對象(Iterable)。

[1]不理解列表推導式(list comprehension)可以點擊這裡:Python 學習筆記 02

>>> mylist = [x*x for x in range(3)]>>> for i in mylist:... print(i)014

像lists, strings, files...這種任何一個可以用for... in...遍歷的對象都稱作可迭代對象(Iterable)。使用這些可迭代對象是非常方便的,因為你可以隨時隨地的去取出你想要的值。但是這些值都是存在內存中的,所以當你有很多值的時候,很可能將會產生不好的效果。

生成器(generators)是一種迭代器(iterators),但是你僅僅可以對他們進行一次迭代,這是因為它們並沒有把所有的值存在內存中,而是在運行時生成值。

>>> mygenerator = (x*x for x in range(3))>>> for i in mygenerator:... print(i)014

除了在迭代器(iterators)使用「[]」,而在 生成器(generators)中使用「()」這一點不同之外,生成器(generators)是和迭代器(iterators)幾乎是一模一樣的,但是你永遠不能進行第二次「for i in mygenerator」操作,因為一個生成器(generators)只能被使用一次:生成器中的內容,訪問一次之後就不能在訪問第二次。

Yield關鍵詞的用法與return的用法幾乎一致,他們只有一個區別: 當某個函數使用Yield時,該函數將返回一個生成器(generators)

>>> 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)014

看看上面這個例子,這個例子說明:createGeneraor()函數將會返回一個值的集合,你僅僅需要去遍歷一次這個集合就可以得到你想要的結果了。

為了精通 yield ,你必須要理解:當你調用這個函數的時候,函數內部的代碼並不立馬執行 ,這個函數只是返回一個生成器對象,這有點蹊蹺不是嗎。那麼,函數內的代碼什麼時候執行呢?當你使用for進行迭代的時候.(感謝@Nicolas-L 提供的譯文。)【原來的翻譯是:要完全的理解yield,你必須搞明白下面這段話:當使用yield的時候,寫在yield後面的代碼並不會執行,它僅僅會返回一個生成器(generators),這段代碼只會在你遍歷這個返回的生成器的時候開始運行。】

現在到了最難理解的地方了:

第一次,for在遍歷上述方法中創建的mylist的時候,循環體內的代碼將從頭開始運行一直到它碰到yield關鍵詞,它將返回循環內的第一個值,然後再次執行循環體內代碼,返回循環內產生的第二個值,一直到這個循環結束。

下面開始講解你給出的代碼。

Generator(生成器)

# 在這裡,你給node對象創建了一個方法,這個方法將會返回一個生成器。def node._get_child_candidates(self, distance, min_dist, max_dist): #在你每次使用生成器(generators)時候會調用下面這段代碼。 #如果node對象還有左子節點,並且深度距離符合下述條件,那麼返回下一個子節點。 if self._leftchild and distance - max_dist < self._median: yield self._leftchild # 如果node對象還有右子節點,並且深度距離符合下述條件,那麼返回下一個子節點。 if self._rightchild and distance + max_dist >= self._median: yield self._rightchild # 如果函數運行到了這裡,說明Generator(生成器)此時已經為空了。

Caller(調用):

# 創建一個空的list和一個當前對象的引用listresult, candidates = list(), [self]# 遍歷candidates,一開始他僅僅包含一個元素while candidates: # 獲取並刪除list中最後位置上的candidate node = candidates.pop() # 獲取candidate到obj之間的距離 distance = node._get_dist(obj) # 如果距離符合下述條件,則把candidate附加到result裡面 if distance <= max_dist and distance >= min_dist: result.extend(node._values) #把candidates list中所有candidate對象的所有子節點都添加到candidates中 candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))return result

================其實到這裡問題已經有了一個比較明確的回答了==================

上面的代碼有小部分要說明的:

  • extend()是list對象的方法,用於在列表末尾一次性追加另一個序列中的多個值。

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

在你的代碼中使用的是一個generator(生成器),這有幾個優點:

  1. 你不要再次去讀取這些值

  2. 可能有很多子節點,你不想讓他們存在內存中

Python不關心某個方法的參數是不是一個列表list,Python預想它是一個可迭代對象(Iterable),所以當這個參數是strings、lists或者generators的時候可以正確執行!這就被稱作duck typing[1]

[1]:Duck typing:是動態類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的介面,而是由當前方法和屬性的集合決定。在 Duck typing中,關注的不是對象的類型本身,而是它是如何使用的。

我們這裡可以先暫停,進一步了解一下generator的用法:

控制一個 generator防止它被耗盡。

>>> class Bank(): # 我們建造一個銀行,並且修建一些ATMs... 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() # its even true for new ATMs>>> 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() # 建造一個新的ATM,並使用。>>> for cash in brand_new_atm:... print cash$100$100$100$100$100$100$100$100$100...

它是非常有用的比如用來控制資源的訪問。

Python的內建模塊itertools提供了非常有用的用於操作迭代對象的函數。你曾經有做過一個generator?關聯兩個generators?並為此感到厭煩嗎?

你只需要import itertools.(導入itertools)(有關於Itertools的更多用法,大家可以訪問: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)]

More about it in this article about how does the for loop work.

希望你們喜歡,如果可以,等待下一期)


推薦閱讀:

Python基礎語法知識總結與實踐(二)
聊聊Python,談談未來
如何評價 Google 開發的,將 Python 轉譯為 Go 的 runtime:Grumpy?
為什麼Pypy沒有被推廣以及取代CPython?
for循環在Python中是怎麼工作的

TAG:Python | 编程 | 学习 |