为什么itertools.product在初始化时会贯穿所有元素?

Zen*_*non 6 iteration generator python-3.7

我假设当时itertools.product生成一个元素。我现在注意到这是不正确的。简单的概念证明:

Class A:
  def __init__(self, n):
    self.source = iter(range(n))

  def __iter__(self):
    return self

  def __next__(self):
    val = next(self.source)
    print("I am at:", val)
    return val
Run Code Online (Sandbox Code Playgroud)

现在,如果我这样做:

from itertools import product
l = product(A(3), A(3))
print("Here")
next(l)
Run Code Online (Sandbox Code Playgroud)

我希望有作为输出:

>'Here'
>'I am at 0'
>'I am at 0'
Run Code Online (Sandbox Code Playgroud)

但是我有

>'I am at 0'
>'I am at 1'
>'I am at 2'
>'I am at 0'
>'I am at 1'
>'I am at 2'
>'Here'
Run Code Online (Sandbox Code Playgroud)

我想念什么吗?

Mas*_*fox 3

为了回答你的问题,我们需要看看itertools.product的实现:

def product(*args, repeat=1):
    pools = [tuple(pool) for pool in args] * repeat
    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]
    for prod in result:
        yield tuple(prod)
Run Code Online (Sandbox Code Playgroud)

在这里你可以找到真正的C实现,但是要回答这个问题,参考python就足够了(参见底部的EXTRA段落)。

重点关注这行代码:

pools = [tuple(pool) for pool in args] * repeat
Run Code Online (Sandbox Code Playgroud)

这样,两个迭代器的所有元素(在输入中获取)都被转换为元组列表(仅在第一次调用时next()),此时它们才真正被创建。

返回到您的代码,当您next(l)第一次调用时,将创建迭代器的所有元素。在您的示例中,将创建polls包含以下元素的列表:

# pools: [(0, 1, 2), (0, 1, 2)]
Run Code Online (Sandbox Code Playgroud)

这就是你得到这些输出的原因。


至于print("Here"),要理解为什么要首先打印它,您需要了解生成器的工作原理:

itertool.product()返回一个生成器对象。生成器在被第一个 刺激之前不会执行功能代码next()。随后,每次调用都next()允许您计算下一个元素,仅执行一次包含关键字 的循环yield

在这里您将找到优秀的资源,以更好地了解 python 生成器的工作原理。


为什么“itertools”选择将元组列表保留在内存中?

因为笛卡尔积必须对同一元素求值多次,而迭代器不能只使用一次。


额外的

在 C 中,它创建的元组列表与 python 等效,正如您从这段代码中看到的那样,它会被急切地求值。每个可迭代参数首先被转换为一个元组:

pools = PyTuple_New(npools);
if (pools == NULL)
    goto error;

for (i=0; i < nargs ; ++i) {
    PyObject *item = PyTuple_GET_ITEM(args, i);
    PyObject *pool = PySequence_Tuple(item);
    if (pool == NULL)
        goto error;
    PyTuple_SET_ITEM(pools, i, pool);
    indices[i] = 0;
}
for ( ; i < npools; ++i) {
    PyObject *pool = PyTuple_GET_ITEM(pools, i - nargs);
    Py_INCREF(pool);
    PyTuple_SET_ITEM(pools, i, pool);
    indices[i] = 0;
}
Run Code Online (Sandbox Code Playgroud)