首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >检测迭代器是否会被消耗

检测迭代器是否会被消耗
EN

Stack Overflow用户
提问于 2013-03-13 08:13:23
回答 5查看 708关注 0票数 13

是否有一种统一的方法来知道迭代是否会消耗一个可迭代对象?

假设您有一个函数crunch,它为参数请求一个可迭代的对象,并多次使用它。类似于:

代码语言:javascript
复制
def crunch (vals):

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

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

如果函数使用可迭代(不是列表)来调用,则会出现问题。在下面的调用中,永远不会执行yum函数:

代码语言:javascript
复制
crunch(iter(range(4))

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

代码语言:javascript
复制
def crunch (vals):
    vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

但是,如果对crunch的调用是:

代码语言:javascript
复制
hugeList = list(longDataStream)
crunch(hugeList)

我们可以通过这样定义crunch来解决这个问题:

代码语言:javascript
复制
def crunch (vals):
    if type(vals) is not list:
        vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

但是仍然存在这样的情况:调用代码将数据存储在

  • 不能消费
  • 不是一张清单

例如:

代码语言:javascript
复制
from collections import deque
hugeDeque = deque(longDataStream)
crunch(hugeDeque)

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

代码语言:javascript
复制
def crunch (vals):
    if isconsumable(vals):
        vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

这个问题有解决办法吗?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-03-13 08:27:53

另一个额外的选项可以是查询迭代器是否是它自己的迭代器:

代码语言:javascript
复制
if iter(vals) is vals:
    vals = list(vals)

因为在这种情况下,它只是一个迭代器。

这适用于生成器、迭代器、文件和许多其他对象,这些对象都是为“一次运行”而设计的,换句话说,所有迭代器本身都是迭代器,因为是__iter__()

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

通常,自用对象将是自己的迭代器,但在某些情况下可能不允许这样做。

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

代码语言:javascript
复制
class ListPart(object):
    """Liste stückweise zerlegen."""
    def __init__(self, data=None):
        if data is None: data = []
        self.data = data
    def next(self):
        try:
            return self.data.pop(0)
        except IndexError:
            raise StopIteration
    def __iter__(self):
        return self
    def __len__(self): # doesn't work with __getattr__...
        return len(self.data)

你把它叫做

代码语言:javascript
复制
l = [1, 2, 3, 4]
lp = ListPart(l)
for i in lp: process(i)
# now l is empty.

如果我现在将额外的数据添加到该列表中,并再次遍历同一个对象,我将得到新的数据,即违反议定书

该协议的意图是,一旦迭代器的next()方法引发StopIteration,它将在随后的调用中继续这样做。不遵守此属性的实现被视为已破坏。(这个约束是在Python2.3中添加的;在Python2.2中,各种迭代器根据这个规则被打破了。)

因此,在这种情况下,对象将不得不返回一个与其本身不同的迭代器,尽管它是自我消耗的。在这种情况下,这可以通过

代码语言:javascript
复制
def __iter__(self):
    while True:
        try:
            yield l.pop(0)
        except IndexError: # pop from empty list
            return

它会在每次迭代中返回一个新生成器--在我们讨论的情况下,这将通过mash来实现。

票数 5
EN

Stack Overflow用户

发布于 2013-03-13 08:21:37

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

请注意,IterableIterator都不是合适的选择,因为这些类型不能保证长度,因此不能保证迭代甚至是有限的,更不用说可重复了。但是,您可以同时检查SizedIterable

重要的是记录您的函数将对其参数进行两次迭代,从而警告用户他们必须传入一个支持该参数的对象。

票数 6
EN

Stack Overflow用户

发布于 2013-03-13 08:21:24

代码语言:javascript
复制
def crunch (vals):
    vals1, vals2 = itertools.tee(vals, 2)

    for v in vals1:
        chomp(v)

    for v in vals2:
        yum(v)

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

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15379994

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档