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,调用该函数将返回一个生成器。现在这是一个真正的惰性版本,因为函数体的执行在请求项目之前不会开始。这意味着内部和外部调用都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版本不同(在上面的代码中取消注释)。现在你会看到
start
start
并且在到达上下文管理器的末尾之前,已经引发了错误。
附带说明一下,为了使您的代码能够正常工作,替换dict需要无效(这是第一个版本),所以我完全不明白您为什么要使用该上下文管理器。其次,dict在 Python 2 中文字没有排序,关键字参数也没有排序,因此也违背了使用OrderedDict. 另请注意,在 Python 3 中,非字符串关键字参数的行为dict已被删除,更新任何键的字典的干净方法是使用dict.update.
添加回答
举报