簡體   English   中英

python生成器的行為不一致

[英]Inconsistent behavior of python generators

下面的python代碼產生[(0,0),(0,7)...(0,693)]而不是預期的元組列表,它組合了3的倍數和7的倍數:

multiples_of_3 = (i*3 for i in range(100))
multiples_of_7 = (i*7 for i in range(100))
list((i,j) for i in multiples_of_3 for j in multiples_of_7)

此代碼修復了此問題:

list((i,j) for i in (i*3 for i in range(100)) for j in (i*7 for i in range(100)))

問題:

  1. 生成器對象似乎扮演迭代器的角色,而不是每次枚舉生成的列表時都提供迭代器對象。 后來的策略似乎被.Net LINQ查詢對象采用。 有一種優雅的方式來解決這個問題嗎?
  2. 為什么第二段代碼有效? 我是否理解在循環遍歷所有7的倍數后,發生器的迭代器不會被重置?
  3. 難道你不認為這種行為是反直覺的,如果不是不一致的話?

生成器對象迭代器,因此是一次性的。 這不是能產生任何數量的獨立迭代的迭代 這種行為不是你可以用某個開關改變的東西,所以任何解決方法相當於使用可迭代(例如列表)而不是生成器或重復構建生成器。

第二個片段是后者。 根據定義,它等同於循環

for i in (i*3 for i in range(100)):
    for j in (i*7 for i in range(100)):
        ...

希望在這里,在外循環的每次迭代中重新評估后一個生成器表達式並不奇怪。

正如您所發現的,生成器表達式創建的對象是一個迭代器(更准確地說是一個生成器迭代器 ),設計為只消耗一次。 如果你需要一個可重置的生成器,只需創建一個真正的生成器並在循環中使用它:

def multiples_of_3():               # generator
    for i in range(100):
       yield i * 3
def multiples_of_7():               # generator
    for i in range(100):
       yield i * 7
list((i,j) for i in multiples_of_3() for j in multiples_of_7())

你的第二個代碼是有效的,因為內循環的表達式列表( (i*7 ...) )是在外循環的每次傳遞上計算的。 這導致每次都創建一個新的生成器迭代器,它為您提供所需的行為,但代價是代碼清晰度。

要了解發生了什么,請記住,當for循環迭代時,迭代器沒有“重置”。 (這是一個特性;這樣的重置會破壞迭代的大型迭代器,對於生成器來說是不可能的。)例如:

multiples_of_2 = iter(xrange(0, 100, 2))  # iterator
for i in multiples_of_2:
    print i
# prints nothing because the iterator is spent
for i in multiples_of_2:
    print i

......而不是這個:

multiples_of_2 = xrange(0, 100, 2)        # iterable sequence, converted to iterator
for i in multiples_of_2:
    print i
# prints again because a new iterator gets created
for i in multiples_of_2:
    print i

生成器表達式等同於被調用的生成器,因此只能迭代一次。

如果要將生成器表達式轉換為多遍迭代,則可以以相當常規的方式完成。 例如:

class MultiPass(object):
    def __init__(self, initfunc):
        self.initfunc = initfunc
    def __iter__(self):
        return self.initfunc()

multiples_of_3 = MultiPass(lambda: (i*3 for i in range(20)))
multiples_of_7 = MultiPass(lambda: (i*7 for i in range(20)))
print list((i,j) for i in multiples_of_3 for j in multiples_of_7)

從定義事物的角度來看,輸入的工作量相似:

def multiples_of_3():
    return (i*3 for i in range(20))

但是從用戶的角度來看,他們編寫multiples_of_3而不是multiples_of_3() ,這意味着對象multiples_of_3與任何其他可迭代對象是多態的,例如tuplelist

鍵入lambda:的需要lambda:有點不優雅,真實。 我不認為在語言中引入“可迭代的理解”會給你提供你想要的東西,同時保持向后兼容性。 但是只有那么多標點字符,我懷疑這會被認為是值得的。

我發現的真正問題是關於單通道和多通道迭代,以及目前沒有標准機制來確定可迭代的單通道還是多通道的事實:請參閱單通道和多通道迭代

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM