1 回答

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)
使用該yield版本,我們會注意到它會打印以下內容:
end of context manager
start
start
即,僅當通過 請求結果時才啟動生成器list(result)。這與return版本不同(在上面的代碼中取消注釋)?,F在你會看到
start
start
并且在到達上下文管理器的末尾之前,已經引發了錯誤。
附帶說明一下,為了使您的代碼能夠正常工作,替換dict需要無效(這是第一個版本),所以我完全不明白您為什么要使用該上下文管理器。其次,dict在 Python 2 中文字沒有排序,關鍵字參數也沒有排序,因此也違背了使用OrderedDict. 另請注意,在 Python 3 中,非字符串關鍵字參數的行為dict已被刪除,更新任何鍵的字典的干凈方法是使用dict.update.
添加回答
舉報