Подтвердить что ты не робот

Сравнение двух генераторов в Python

Мне интересно об использовании == при сравнении двух генераторов

Например:

x = ['1','2','3','4','5']

gen_1 = (int(ele) for ele in x)
gen_2 = (int(ele) for ele in x)

gen_1 и gen_2 одинаковы для всех практических целей, и все же, когда я их сравниваю:

>>> gen_1 == gen_2
False

Мое предположение заключается в том, что == здесь обрабатывается как is обычно, и поскольку gen_1 и gen_2 расположены в разных местах в памяти:

>>> gen_1
<generator object <genexpr> at 0x01E8BAA8>
>>> gen_2
<generator object <genexpr> at 0x01EEE4B8>

их сравнение оценивается как False. Правильно ли я думаю об этом? И любое другое понимание приветствуется.

И, кстати, я знаю, как сравнивать два генератора:

>>> all(a == b for a,b in zip(gen_1, gen_2))
True

или даже

>>> list(gen_1) == list(gen_2)
True

Но если есть лучший способ, я хотел бы знать.

4b9b3361

Ответ 1

Вы правы с вашей догадкой - резерв для сравнения типов, которые не определяют ==, - это сравнение, основанное на идентичности объекта.

Лучшим способом сравнения значений, которые они генерируют, будет

from itertools import izip_longest, tee
sentinel = object()
all(a == b for a, b in izip_longest(gen_1, gen_2, fillvalue=sentinel))

Это может привести к короткому замыканию, не обязательно смотреть на все значения. Как отмечают larsmans в комментариях, мы не можем использовать izip() здесь, так как это может привести к неправильным результатам, если генераторы производят другое число элементов - izip() остановится на самом коротком итераторе. Мы используем только что созданный экземпляр object как значение заполнения для izip_longest(), так как экземпляры object также сравниваются по идентификатору объекта, поэтому sentinel гарантированно сравнится неравномерно со всем остальным.

Обратите внимание, что нет возможности сравнивать генераторы без изменения их состояния. Вы можете хранить предметы, которые были израсходованы, если они вам понадобятся позже:

gen_1, gen_1_teed = tee(gen_1)
gen_2, gen_2_teed = tee(gen_2)
all(a == b for a, b in izip_longest(gen_1, gen_2, fillvalue=sentinel))

Это приведет к тому, что состояние gen_1 и gen_2 существенно не изменится. Все значения, потребляемые all(), хранятся внутри объекта tee.

В этот момент вы можете спросить себя, действительно ли стоит использовать ленивые генераторы для приложения под рукой - может быть, лучше просто преобразовать их в списки и работать с списками вместо этого.

Ответ 2

Поскольку генераторы генерируют свои значения по требованию, нет никакого способа "сравнить" их, фактически не потребляя их. И если ваши генераторы генерируют бесконечную последовательность значений, такой тест равенства, который вы предлагаете, будет бесполезным.

Ответ 3

== действительно совпадает с is на двух генераторах, потому что это единственная проверка, которая может быть выполнена без изменения их состояния и, следовательно, потери элементов.

list(gen_1) == list(gen_2)

- надежный и общий способ сравнения двух конечных генераторов (но, очевидно, потребляет оба); ваше решение на основе zip терпит неудачу, если они не генерируют равное количество элементов:

>>> list(zip([1,2,3,4], [1,2,3]))
[(1, 1), (2, 2), (3, 3)]
>>> all(a == b for a, b in zip([1,2,3,4], [1,2,3]))
True

Решение на основе list все еще не работает, когда любой генератор генерирует бесконечное количество элементов. Вы можете придумать обходной путь для этого, но когда оба генератора бесконечны, вы можете создавать semi-algorithm для неравновесия.

Ответ 4

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