Почему при инициализации itertools.product проходит через все элементы? - PullRequest
6 голосов
/ 07 октября 2019

Я предположил, что 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

Сейчас Если я сделаю:

from itertools import product
l = product(A(3), A(3))
print("Here")
next(l)

Я ожидаю получить в качестве вывода:

>'Here'
>'I am at 0'
>'I am at 0'

Но у меня есть

>'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'

Я что-то упустил?

Ответы [ 2 ]

3 голосов
/ 09 октября 2019

Чтобы ответить на ваш вопрос, нам нужно взглянуть на реализацию 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)

здесь вы найдете реальную реализацию C, но чтобыЧтобы ответить на этот вопрос, достаточно сослаться на python (см. ДОПОЛНИТЕЛЬНЫЙ абзац внизу).

сосредоточиться на этой строке кода:

pools = [tuple(pool) for pool in args] * repeat

таким образом, все элементыдва итератора (взятые во входных данных) преобразуются в список кортежей (только при первом вызове next()), и в это время они фактически создаются.

Возвращаясь к вашему коду, когда вы вызываетеnext(l) впервые созданы все элементы итераторов. В вашем примере будет создан список polls со следующими элементами:

# pools: [(0, 1, 2), (0, 1, 2)]

, поэтому вы получили эти выходные данные.


Что касается print("Here")Чтобы понять, почему он печатается первым, вам нужно понять, как работают генераторы:

itertool.product() возвращает объект генератора. Генератор не выполняет код функции, пока он не будет стимулирован первым next(). Впоследствии каждый вызов next() позволяет вычислять следующий элемент, выполняя только один раз цикл, содержащий ключевое слово yield.

Здесь вы найдете отличные ресурсы, чтобы лучше понять, как работает pythonгенераторы работают.


Почему 'itertools' решил сохранить список кортежей в памяти?

Поскольку декартово произведение должно оценивать один и тот же элемент несколько раз, и вместо этого итераторы не могут бытьпотребляется только один раз.


EXTRA

в 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;
}
2 голосов
/ 09 октября 2019

Я хотел бы отметить, что в то время как для обоих экземпляров class A метод __next__ вызывается исчерпывающе (пока не встречается StopIteration), итератор itertools.product все еще лениво оценивается с последующими вызовамиnext. Обратите внимание:

> 'Я нахожусь на 0'

> 'Я нахожусь на 1'

> 'Я нахожусь на 2'

> «Я на 0»

> «Я на 1»

> «Я на 2»

> «Здесь»

является просто результатом исчерпывающего вызова next сначала для первого пропущенного экземпляра, а затем для второго. Это легче увидеть при вызове product(A(2), A(3)), что приводит к:

> 'Я нахожусь на 0'

> 'Я нахожусь на 1'

> «Я нахожусь на 0»

> «Я нахожусь на 1»

> «Я нахожусь на 2»

Такое же поведение наблюдается для combinations и permutations. На самом деле в поисках столь информированного вопроса с "itertools.product оценивает свои аргументы лениво?"привел меня к этому такому вопросу , который также отвечает на ваш вопрос. Аргументы не оцениваются лениво:

, поскольку product иногда необходимо пройти итерацию более одного раза, что невозможно, если аргументы были оставлены в качестве итераторов, которые могут быть использованы только один раз.

...