检测是否将使用迭代器

Dac*_*cav 13 python

是否有统一的方法来了解迭代是否会使用可迭代对象?

假设您有一个特定的函数crunch,它要求参数的可迭代对象,并多次使用它.就像是:

def crunch (vals):

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)
Run Code Online (Sandbox Code Playgroud)

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

如果使用不是列表的iterable调用函数,则会出现问题.在以下调用中,yum函数永远不会执行:

crunch(iter(range(4))
Run Code Online (Sandbox Code Playgroud)

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

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

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)
Run Code Online (Sandbox Code Playgroud)

但如果调用的话,这将导致使用两倍的内存crunch:

hugeList = list(longDataStream)
crunch(hugeList)
Run Code Online (Sandbox Code Playgroud)

我们可以通过这样定义来解决这个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)
Run Code Online (Sandbox Code Playgroud)

但仍然存在colud,其中调用代码将数据存储在某些内容中

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

例如:

from collections import deque
hugeDeque = deque(longDataStream)
crunch(hugeDeque)
Run Code Online (Sandbox Code Playgroud)

有一个isconsumable谓词会很好,所以我们可以crunch像这样定义:

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

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)
Run Code Online (Sandbox Code Playgroud)

有这个问题的解决方案吗?

Bre*_*arn 6

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

请注意,既不是Iterable也不Iterator是合适的选择,因为这些类型不保证长度,因此不能保证迭代甚至是有限的,更不用说可重复了.但是,您可以检查两者SizedIterable.

重要的是要记录你的函数将迭代它的参数两次,从而警告用户必须传入一个支持它的对象.


glg*_*lgl 5

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

if iter(vals) is vals:
    vals = list(vals)
Run Code Online (Sandbox Code Playgroud)

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

这适用于生成器,迭代器,文件和许多其他为"一次运行"设计的对象,换句话说,所有迭代器本身都是迭代器,因为迭代器self从它返回__iter__().

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


通常,一个自耗对象将是它自己的迭代器,但有些情况下可能不允许这样做.

想象一个包装列表并在迭代中清空此列表的类,例如

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)
Run Code Online (Sandbox Code Playgroud)

你称之为

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

如果我现在将其他数据添加到该列表并再次遍历同一个对象,我将获得违反协议的新数据:

该协议的目的是一旦迭代器的next()方法提出StopIteration,它将继续在后续调用中这样做.不遵守此属性的实现被视为已损坏.(这个约束是在Python 2.3中添加的;在Python 2.2中,根据此规则会破坏各种迭代器.)

因此,在这种情况下,对象必须返回与自身不同的迭代器,尽管它是自耗的.在这种情况下,可以这样做

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

它会在每次迭代时返回一个新的生成器 - 在我们讨论的情况下,它会通过mash掉落.