这zip
与docs中给出的实现等效
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
得到满足,函数执行return并停止。现在next(gen1)
返回9。
在您的第二个示例中gen1 = gen(8)
,gen2 = gen(10)
。在两个发生器都消耗完之后,直到第7次迭代。现在在第8次迭代gen1
调用elem = next(it, sentinel)
中返回并满足sentinel
(因为此时gen1
已耗尽)并if elem is sentinel
得到满足,并且该函数执行return和stop。现在next(gen2)
返回8。
受疯狂物理学家的回答启发,您可以使用此Gen
包装器来解决该问题:
编辑:处理由指出的情况让-弗朗索瓦·T。
一旦从迭代器中消耗了一个值,该值就从迭代器中消失了,并且没有迭代器的就地变异方法将其添加回迭代器中。一种解决方法是存储最后消耗的值。
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`