これは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)
あなたの第1の例では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
が満たされ、関数はreturnを実行して停止します。今next(gen1)
9を返します。
あなたの第二の例ではgen1 = gen(8)
とgen2 = gen(10)
。両方のジェネレータが7番目の反復まで消費された後。これで、8回目の反復gen1
呼び出しelem = next(it, sentinel)
で戻りますsentinel
(この時点gen1
で使い果たされたため)。これif elem is sentinel
は満たされ、関数はreturnを実行して停止します。現在next(gen2)
は8を返します。
Mad Physicistの回答に触発されて、このGen
ラッパーを使用してそれを打ち消すことができます。
編集:Jean-Francois Tが指摘したケースを処理するため。
イテレーターから値が消費されると、その値はイテレーターから永久に失われ、イテレーターがイテレーターに戻すためのインプレースの変更メソッドはありません。回避策の1つは、最後に消費された値を保存することです。
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`