Сравнение двух генераторов в 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
Но если есть лучший способ, я хотел бы знать.
Ответы
Ответ 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 должен был бы использовать их как полностью (ну, тем короче, так или иначе). Я считаю, что хорошо, что вы должны делать это явно, тем более что тот или иной может быть бесконечным.