Я хочу проанализировать 2 генератора (потенциально) разной длины с помощью zip
:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
Однако, если gen2
есть меньше элементов, один дополнительный элемент gen1
«потребляется».
Например,
def my_gen(n:int):
for i in range(n):
yield i
gen1 = my_gen(10)
gen2 = my_gen(8)
list(zip(gen1, gen2)) # Last tuple is (7, 7)
print(next(gen1)) # printed value is "9" => 8 is missing
gen1 = my_gen(8)
gen2 = my_gen(10)
list(zip(gen1, gen2)) # Last tuple is (7, 7)
print(next(gen2)) # printed value is "8" => OK
Очевидно, что значение отсутствует ( 8
в моем предыдущем примере), потому что gen1
читается (таким образом генерируя значение 8
) до того, как оно осознает gen2
, что в нем больше нет элементов. Но эта ценность исчезает во вселенной. Когда gen2
«дольше», такой «проблемы» нет.
ВОПРОС : Есть ли способ получить это пропущенное значение (т.е. 8
в моем предыдущем примере)? ... в идеале с переменным количеством аргументов (как это zip
делает).
ПРИМЕЧАНИЕ : я в настоящее время реализовал по-другому с помощью, itertools.zip_longest
но мне действительно интересно, как получить это пропущенное значение, используя zip
или эквивалентный.
ПРИМЕЧАНИЕ 2 : Я создал несколько тестов различных реализаций в этом REPL на случай, если вы захотите отправить и попробовать новую реализацию :) https://repl.it/@jfthuong/MadPhysicistChester
источник
zip()
прочитал8
сgen1
, это пошло.Ответы:
Один из способов - реализовать генератор, который позволит вам кэшировать последнее значение:
Чтобы использовать это, оберните входы в
zip
:Важно сделать
gen2
итератор, а не итеративный, чтобы вы могли знать, какой из них был исчерпан. Еслиgen2
исчерпан, вам не нужно проверятьgen1.last
.Другой подход заключается в переопределении zip для принятия изменяемой последовательности итераций вместо отдельных итераций. Это позволит вам заменить итерации на цепочечную версию, включающую ваш «заглядывающий» элемент:
Такой подход проблематичен по многим причинам. Он не только потеряет исходную итерацию, но и потеряет все полезные свойства, которые мог иметь исходный объект, заменив его
chain
объектом.источник
cache_last
, и тот факт, что он не меняетnext
поведение ... так плохо, что это не симметрично (переключениеgen1
иgen2
в zip приведет к разным результатам). Приветствияlast
вызовы после его исчерпания. Это должно помочь выяснить, нужно ли вам последнее значение или нет. Также делает его более производительным.print(gen1.last) print(next(gen1))
ISNone and 9
last
.Это
zip
эквивалент реализации, указанный в документацииВ вашем первом примере
gen1 = my_gen(10)
иgen2 = my_gen(8)
. После того как оба генератора расходуются до 7-й итерации. Теперь в 8-ой итерацииgen1
вызовы,elem = next(it, sentinel)
которые возвращают 8, но когдаgen2
вызовыelem = next(it, sentinel)
возвращаютсяsentinel
(потому что при этомgen2
исчерпаны) иif elem is sentinel
выполняются, а функция выполняет возврат и останавливается. Сейчасnext(gen1)
возвращается 9.В вашем втором примере
gen1 = gen(8)
иgen2 = gen(10)
. После того как оба генератора расходуются до 7-й итерации. Теперь в 8-й итерацииgen1
вызовы,elem = next(it, sentinel)
которые возвращаютсяsentinel
(потому что в этот моментgen1
исчерпаны) иif elem is sentinel
выполняются, а функция выполняет возврат и останавливается. Теперьnext(gen2)
возвращает 8.Вдохновленный ответом Безумного Физика , вы можете использовать эту
Gen
обертку, чтобы противостоять ей:Изменить : для обработки случаев, указанных Жан-Франсуа Т.
Как только значение получено из итератора, оно навсегда исчезает из итератора, и для итераторов не существует метода мутирования на месте, чтобы добавить его обратно в итератор. Один из способов - сохранить последнее использованное значение.
Примеры:
источник
gen1 = cache_last(range(0))
иgen2 = cache_last(range(2))
после этогоlist(zip(gen1, gen2)
, вызовnext(gen2)
вызоветAttributeError: 'cache_last' object has no attribute 'prev'
. # 2. Если gen1 длиннее, чем gen2, после использования всех элементовnext(gen2)
будет продолжать возвращать последнее значение вместоStopIteration
. Я отмечу MadPhysicist ответ и ответ. Спасибо!Я вижу, вы уже нашли этот ответ, и он упоминался в комментариях, но я решил, что из этого получу ответ. Вы хотите использовать
itertools.zip_longest()
, который заменит пустые значения более короткого генератора наNone
:Печать:
Вы также можете указать
fillvalue
аргумент при вызовеzip_longest
для замены наNone
значение по умолчанию, но в основном для вашего решения, когда вы нажметеNone
(или,i
илиj
) в цикле for, другая переменная будет иметь вашу8
.источник
zip_longest
и это было в моем вопросе на самом деле. :)Вдохновленные разъяснениями @ GrandPhuba о
zip
, давайте создадим «безопасный» вариант ( здесь протестировано на модуле ):Вот базовый тест:
источник
Вы можете использовать itertools.tee и itertools.islice :
источник
Если вы хотите повторно использовать код, самое простое решение:
Вы можете проверить этот код, используя ваши настройки:
Он напечатает:
источник
Я не думаю, что вы можете получить отброшенное значение с помощью базового цикла for, потому что исчерпан итератор, взятый из
zip(..., ...).__iter__
отброшенного после исчерпания, и вы не можете получить к нему доступ.Вы должны поменять свой почтовый индекс, тогда вы можете получить позицию брошенного предмета с каким-нибудь хакерским кодом)
источник