如何在同一生成器上使用不同迭代器的"zip"(使用 itertools.tee)不再运行生成器?



我不明白zip是如何工作的。它怎么不运行";calc";两次它是如何";知道";是同一次迭代吗?

In [1]: import itertools
In [2]: def calc():
...:     for i in range(5):
...:         print(i)
...:         yield i
...:
In [3]: i1, i2 = itertools.tee(calc())
In [4]: z = zip(i1, i2)
In [5]: for i in z:
...:     print(i)
...:
0
(0, 0)
1
(1, 1)
2
(2, 2)
3
(3, 3)
4
(4, 4)

我发现我不够清楚。我知道伊里德做什么,这不是问题所在。在每一次迭代中,我们都应该对calc进行两次迭代,但如果你看看";打印";你可以看到它只发生过一次。当我在"zip"之前使用next(i1)时也会发生同样的情况,这让它变得更加奇怪。

在您的情况下,因为您运行了下一次,所以您在第一个迭代器(i1(中消耗了一次迭代,该迭代器为零,因此zip以1开头表示i1,以0开头表示i2

请参阅下面类似的测试用例,在运行和不运行的情况下,演示为什么在您的情况下使用[1,2,4]压缩[0,1,2,3]:

#
# With running next
#
>>> import itertools
>>> a = [1,2,3,4]
>>> b, c = itertools.tee(a)
>>> b
<itertools._tee object at 0x000001D5A60D7C80>
>>> next(b)
1
>>> next(b)
2
>>> zip(b,c)
<zip object at 0x000001D5A60D7D00>
>>> for i in zip(b,c):
...  print(i)
...
(3, 1)
(4, 2)

#
# Without running next
#
>>> a = [1,2,3,4]
>>> b, c = itertools.tee(a)
>>> for i in zip(b,c):
...  print(i)
... 
(1, 1)
(2, 2)
(3, 3)
(4, 4)
>>>   
"""
calc() is a generator. Generators by design maintain the state between calls. `
"""
import itertools

def calc():
for i in range(5):
print(i)
yield i

i1, i2 = itertools.tee(calc())
print(f'{type(i1)=}')
print(f'{type(i2)=}')
# itertools.tee Return n independent iterators from a single iterable. i.e.calc
next(i1)  # calc called yields the 1st result.
z = zip(i1, i2)  # calc called again here as a single iterable
# Calc has now yielded the last 4 values.
for i in z:
print(i) 
# The yield function maintains state so:
next(i1)  # Returns a Traceback

输出

type(i1)=<class 'itertools._tee'>
type(i2)=<class 'itertools._tee'>
0
1
(1, 0)
2
(2, 1)
3
(3, 2)
4
(4, 3)
Traceback (most recent call last):
File "C:UsersctyndOneDriveCodeBaseStackOverflowActivityOldScratchesscratch_2.py", line 26, in <module>
next(i1)  # Returns a Traceback
StopIteration

"魔术"发生在itertools.tee中。CCD_ 3不返回"0";常规的";迭代器:

>>> type(i1)
<class 'itertools._tee'>

迭代器i1i2仍然是相关的,并且不会在每次迭代中再次调用该函数。迭代后(如果是引导迭代(,从"calc"返回的值将被保存,并从tee返回的其他迭代器返回到即将到来的匹配迭代(可以是2个以上的迭代器。请参阅itertools.tee(。

例如:在print方法的帮助下,我们可以看到calc只被调用了一次,尽管我用每个迭代器迭代了一次。注意第一次迭代和第二次迭代的输出之间的差异;我计算出";只打印了一次,这意味着生成器只调用了一次。

>>> import itertools
>>> def calc():
...     for i in range(5):
...         print("I calculated: " + str(i))
...         yield i
...
>>> i1, i2 = itertools.tee(calc())
>>> next(i1)
I calculated: 0
0
>>> next(i2)
0

如果你想看看实现,这里有一个python中的实现,并有一个解释。以下是itertools(带tee(的真实实现。这个模块是用c编写的。试着看看tee_fromiterableitertools__tee_impl

看起来zip在区分不同的"itertools.tee";来自同一个tee调用的对象,因此决定不需要调用calc((两次。另一方面,如果您像下面的代码中那样两次调用tee本身,您将看到zip将不再知道输入不相同,因此它将两次调用calc((。

>>> i1, i2 = itertools.tee(calc())
>>> i3,i4=itertools.tee(calc())
>>> z = zip(i1,i3)
>>> for i in z:
...  print(i)
... 
0
0
(0, 0)
1
1
(1, 1)
2
2
(2, 2)
3
3
(3, 3)
4
4
(4, 4)
>>>

最新更新