Это zip
эквивалент реализации, указанный в документах
def zip(*iterables):
# zip('ABCD', 'xy') --> Ax By
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result = []
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
return
result.append(elem)
yield tuple(result)
В вашем первом примере 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
, чтобы противостоять ей:
Редактировать : Для обработки случаев, указанных Жан-Франсуа Т.
Как только значение получено из итератора, оно исчезает из итератора навсегда, и для итераторов не существует метода мутирования на месте для добавьте его обратно в итератор. Обходной путь - сохранить последнее использованное значение.
class Gen:
def __init__(self,iterable):
self.d = iter(iterable)
self.sentinal = object()
self.prev = self.sentinal
def __iter__(self):
return self
@property
def last_val_consumed(self):
if self.prev is None:
raise StopIteration
if self.prev == self.sentinal:
raise ValueError('Nothing has been consumed')
return self.prev
def __next__(self):
self.prev = next(self.d,None)
if self.prev is None:
raise StopIteration
return self.prev
Примеры:
# When `gen1` is larger than `gen2`
gen1 = Gen(range(10))
gen2 = Gen(range(8))
list(zip(gen1,gen2))
# [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7)]
gen1.last_val_consumed
# 8 #as it was the last values consumed
next(gen1)
# 9
gen1.last_val_consumed
# 9
# 2. When `gen1` or `gen2` is empty
gen1 = Gen(range(0))
gen2 = Gen(range(5))
list(zip(gen1,gen2))
gen1.last_val_consumed
# StopIteration error is raised
gen2.last_val_consumed
# ValueError is raised saying `ValueError: Nothing has been consumed`