检测迭代器是否将被消耗

2024-09-30 20:22:30 发布

您现在位置:Python中文网/ 问答频道 /正文

有没有一种统一的方法可以知道迭代是否会消耗iterable对象?在

假设您有一个函数crunch,它请求一个iterable对象作为参数,并多次使用它。比如:

def crunch (vals):

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

(注意:合并两个for循环不是一个选项)。在

如果使用不是列表的iterable调用函数,则会出现问题。在下面的调用中,yum函数从不执行:

^{pr2}$

原则上,我们可以通过如下重新定义crunch函数来解决此问题:

def crunch (vals):
    vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

但是如果对crunch的调用是:

hugeList = list(longDataStream)
crunch(hugeList)

我们可以通过如下定义crunch来解决此问题:

def crunch (vals):
    if type(vals) is not list:
        vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

但是仍然存在调用代码将数据存储在

  • 无法消费
  • 不是列表

例如:

from collections import deque
hugeDeque = deque(longDataStream)
crunch(hugeDeque)

最好有一个isconsumable谓词,这样我们就可以像这样定义crunch

def crunch (vals):
    if isconsumable(vals):
        vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

这个问题有解决办法吗?在


Tags: 对象函数in列表for定义defiterable
3条回答

另一个附加选项是查询iterable是否是它自己的迭代器:

if iter(vals) is vals:
    vals = list(vals)

因为在本例中,它只是一个迭代器。在

这适用于生成器、迭代器、文件和许多其他为“一次运行”而设计的对象,换句话说,所有iterable本身就是迭代器,因为iterator returns ^{} from its ^{}。在

但这可能还不够,因为有些对象在迭代时会清空自己,而不是自己的迭代器。在


通常,一个自消费对象将是它自己的迭代器,但在某些情况下这可能是不允许的。在

假设有一个类包装了一个列表并在迭代时清空这个列表,比如

^{pr2}$

你叫它什么

l = [1, 2, 3, 4]
lp = ListPart(l)
for i in lp: process(i)
# now l is empty.

如果我现在向该列表中添加额外的数据并再次迭代相同的对象,我将得到新的数据,即breach of the protocol

The intention of the protocol is that once an iterator’s next() method raises StopIteration, it will continue to do so on subsequent calls. Implementations that do not obey this property are deemed broken. (This constraint was added in Python 2.3; in Python 2.2, various iterators are broken according to this rule.)

因此,在这种情况下,对象必须返回一个与自身不同的迭代器,尽管它是自消耗的。在这种情况下,可以用

def __iter__(self):
    while True:
        try:
            yield l.pop(0)
        except IndexError: # pop from empty list
            return

它在每次迭代中都返回一个新的生成器—在我们讨论的案例中,这个生成器可能会失败。在

一种可能是使用isinstance(val, collections.Sequence)测试该项是否为序列。非消耗性仍然不能完全保证,但我认为这是你能得到的最好的。Python序列必须有一个长度,这意味着至少它不能是一个开放的迭代器,并且通常意味着必须提前知道元素,这反过来意味着可以在不消耗元素的情况下迭代它们。仍然可以编写符合序列协议但不可编辑的病理类,但您永远无法处理这些类。在

注意,Iterable和{}都不是合适的选择,因为这些类型不能保证长度,因此不能保证迭代是有限的,更不用说可重复了。但是,您可以同时检查Sized和{}。在

重要的是要记录下你的函数将迭代它的参数两次,从而警告用户他们必须传入一个支持此操作的对象。在

def crunch (vals):
    vals1, vals2 = itertools.tee(vals, 2)

    for v in vals1:
        chomp(v)

    for v in vals2:
        yum(v)

在这种情况下,tee将在内部存储{}的完整性,因为一个迭代器在另一个迭代器启动之前完成

相关问题 更多 >