我正在尝试使用itertools.tee来知道迭代器是否为空而不完全消耗它:
from itertools import tee
def get_iterator(i):
i1, i2 = tee(i, 2)
if next(i1, None) is None:
# iterator is empty - raises some error
pass
return i2 # return not empty iterator to caller
正如docs的发球状态:
This itertool may require significant auxiliary storage (depending on how much temporary data needs to be stored). In general, if one iterator uses most or all of the data before another iterator starts, it is faster to use list() instead of tee().
所以很明显,当我不是空的时候,i2在i1之前使用了大部分数据.
一个简单的del可以克服这个吗?:
from itertools import tee
def get_iterator(i):
i1, i2 = tee(i, 2)
if next(i1, None) is None:
# iterator is empty - raises some error
pass
del i1 # Does this overcome storage issue?
return i2 # return not empty iterator to caller
有没有更好的方法来实现这一目标?
提前致谢!
解决方法:
这有点微妙 – 它取决于tee函数的未记录属性以及garbage collector的故意模糊属性.示例Python代码将存储从创建迭代器的点开始直到它们被每个迭代器使用的所有项目但是人们可能很容易想象迭代器会产生清理效果,从而放弃对队列中数据的声明.但即便如此,德尔也会删除你的名字;它不能保证对象的破坏.这样的清理因此可以起作用,但不一定在你期望的时候.知道是否会发生这种情况需要阅读the source code for tee
.它确实对各个迭代器提供了weak reference支持,这表明可以通过这种方式进行优化.
tee_next
的CPython代码相当简单;它提供了一个teedataobject的参考,这是一批最多57项,也形成一个单链表.因此,正常引用计数语义适用于该批次级别.所以基本上,对于CPython,即使它们已被所有迭代器使用,也会在内存中保留多达56个项目,但不会超过这个,因为引用计数处理是立即的.只要存在tee迭代器,就可以保存它们之间的任意数量的项目,但它们不会从源迭代器中读取;至少有一个tee迭代器必须通过teedataobject_getitem
获取这些项目.
所以基本的结论是:是的,del将在CPython中工作,但是使用tee意味着你暂时存储57个项目的批次,而不是1.重复这个方法可能会导致任意数量的这样的窗口 – 除了tee迭代器是可复制的,并且将分享他们的基础列表.
这是CPython的一个版本(4243df51fe43)的具体解释.实施方案将有所不同,例如PyPy,IronPython,Jython或其他版本的CPython.
例如,PyPy’s tee(版本cadf868)使用类似的链接列表,每个链接有一个项目,因此不会批量处理此CPython版本的方式.
有一个值得注意的快捷方式可以防止这种成本增长:我检查的tee实现都生成了可复制的迭代器,并且还复制了可复制的迭代器.因此,重复应用tee不会创建新的迭代器层,这是链式方法的潜在问题.