亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

生成器表達式與生成器函數以及令人驚訝的急切評估

生成器表達式與生成器函數以及令人驚訝的急切評估

慕神8447489 2022-06-28 16:18:57
由于不相關的原因,我以某種方式組合了一些數據結構,同時還將 Python 2.7 的默認設置替換dict為OrderedDict. 數據結構使用元組作為字典中的鍵。請忽略那些細節(dict類型的替換在下面沒有用,但在真實代碼中)。import __builtin__import collectionsimport contextlibimport itertoolsdef combine(config_a, config_b):    return (dict(first, **second) for first, second in itertools.product(config_a, config_b))@contextlib.contextmanagerdef dict_as_ordereddict():    dict_orig = __builtin__.dict    try:        __builtin__.dict = collections.OrderedDict        yield    finally:        __builtin__.dict = dict_orig這最初按預期工作(dict可以將非字符串關鍵字參數作為特例):print 'one level nesting'with dict_as_ordereddict():    result = combine(        [{(0, 1): 'a', (2, 3): 'b'}],        [{(4, 5): 'c', (6, 7): 'd'}]    )print list(result)print輸出:one level nesting[{(0, 1): 'a', (4, 5): 'c', (2, 3): 'b', (6, 7): 'd'}]但是,當嵌套調用combine生成器表達式時,可以看出dict引用被視為OrderedDict,缺少dict使用元組作為關鍵字參數的特殊行為:print 'two level nesting'with dict_as_ordereddict():    result = combine(combine(        [{(0, 1): 'a', (2, 3): 'b'}],        [{(4, 5): 'c', (6, 7): 'd'}]    ),        [{(8, 9): 'e', (10, 11): 'f'}]    )print list(result)print輸出:two level nestingTraceback (most recent call last):  File "test.py", line 36, in <module>    [{(8, 9): 'e', (10, 11): 'f'}]  File "test.py", line 8, in combine    return (dict(first, **second) for first, second in itertools.product(config_a, config_b))  File "test.py", line 8, in <genexpr>    return (dict(first, **second) for first, second in itertools.product(config_a, config_b))TypeError: __init__() keywords must be strings此外,實現 viayield而不是生成器表達式解決了這個問題:def combine_yield(config_a, config_b):    for first, second in itertools.product(config_a, config_b):        yield dict(first, **second)問題:為什么生成器表達式中的某些項目(僅第一個?)在第二個示例中需要之前被評估,或者它需要什么?為什么在第一個示例中沒有對其進行評估?我實際上期望兩者都有這種行為。為什么yield基于 - 的版本有效?
查看完整描述

1 回答

?
holdtom

TA貢獻1805條經驗 獲得超10個贊

在進入細節之前,請注意以下幾點:itertools.product評估迭代器參數以計算產品。這可以從文檔中等效的 Python 實現中看出(第一行是相關的):


def product(*args, **kwds):

    pools = map(tuple, args) * kwds.get('repeat', 1)

    ...

您也可以使用自定義類和簡短的測試腳本來嘗試:


import itertools



class Test:

    def __init__(self):

        self.x = 0


    def __iter__(self):

        return self


    def next(self):

        print('next item requested')

        if self.x < 5:

            self.x += 1

            return self.x

        raise StopIteration()



t = Test()

itertools.product(t, t)

創建itertools.product對象將在輸出中顯示立即請求所有迭代器項。


這意味著,只要您調用itertools.product迭代器參數就會被評估。這很重要,因為在第一種情況下,參數只是兩個列表,所以沒有問題。然后在上下文管理器返回后評估最終的resultvia ,因此所有調用都將被解析為正常的 builtin 。list(result dict_as_ordereddictdictdict


現在對于第二個示例,內部調用combine仍然可以正常工作,現在返回一個生成器表達式,然后將其用作第二個combine調用的參數之一itertools.product。正如我們在上面看到的,這些參數被立即評估,因此生成器對象被要求生成它的值。為此,它需要解決dict. 但是現在我們仍然在上下文管理器dict_as_ordereddict中,因此dict將被解析為OrderedDict不接受關鍵字參數的非字符串鍵。


需要注意的是,第一個使用的版本return需要創建生成器對象才能返回它。這涉及創建itertools.product對象。這意味著這個版本和itertools.product.


現在來回答為什么該yield版本有效的問題。通過使用yield,調用該函數將返回一個生成器?,F在這是一個真正的惰性版本,因為函數體的執行在請求項目之前不會開始。這意味著內部和外部調用都convert不會開始執行函數體并因此調用itertools.product,直到通過list(result). 您可以通過在該函數內并在上下文管理器后面放置一個額外的打印語句來檢查:


def combine(config_a, config_b):

    print 'start'

    # return (dict(first, **second) for first, second in itertools.product(config_a, config_b))

    for first, second in itertools.product(config_a, config_b):

        yield dict(first, **second)


with dict_as_ordereddict():

    result = combine(combine(

        [{(0, 1): 'a', (2, 3): 'b'}],

        [{(4, 5): 'c', (6, 7): 'd'}]

    ),

        [{(8, 9): 'e', (10, 11): 'f'}]

    )

print 'end of context manager'

print list(result)

print

使用該yield版本,我們會注意到它會打印以下內容:


end of context manager

start

start

即,僅當通過 請求結果時才啟動生成器list(result)。這與return版本不同(在上面的代碼中取消注釋)?,F在你會看到


start

start

并且在到達上下文管理器的末尾之前,已經引發了錯誤。


附帶說明一下,為了使您的代碼能夠正常工作,替換dict需要無效(這是第一個版本),所以我完全不明白您為什么要使用該上下文管理器。其次,dict在 Python 2 中文字沒有排序,關鍵字參數也沒有排序,因此也違背了使用OrderedDict. 另請注意,在 Python 3 中,非字符串關鍵字參數的行為dict已被刪除,更新任何鍵的字典的干凈方法是使用dict.update.


查看完整回答
反對 回復 2022-06-28
  • 1 回答
  • 0 關注
  • 92 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號