尽可能避免将迭代器转换为列表

时间:2016-11-06 10:31:18

标签: python iterator

假设我有一个函数将一个参数 iterable 作为输入,我想在迭代上多次迭代。

如果我这样写:

def a_function(an_iterable):
    for x in an_iterable:
       print(x)
    for x in an_iterable:
       print(x)

第二个循环可以执行或不执行。

  • 如果可迭代是listsetdict,它们不是迭代器/生成器函数,或range重新启动自身
  • ,则执行
  • 不是自定义生成器函数或文件对象(使用f=open("file")获得)。 (重复使用文件迭代器是SO的许多问题的主题)

当然,我可以这样做,以避免在不需要时创建不必要的list

def a_function(an_iterable):
    if any(lambda x : type(an_iterable==x) for x in (range,list,set,dict))):
        # use as-is
        pass
    else:
         an_iterable = list(an_iterable)

    for x in an_iterable:
       print(x)
    for x in an_iterable:
       print(x)

适用于很多常见情况,但不适用于一般情况。

有没有一种干净的方法来检测我是否可以在我的可迭代对象上多次迭代?

2 个答案:

答案 0 :(得分:1)

您可以使用collections.abc.Sequence类来查看迭代是否实际上是一个序列:

>>> from collections.abc import Sequence
>>> isinstance([1,2,3], Sequence)
True
>>> isinstance((1,2,3), Sequence)
True
>>> isinstance(range(10), Sequence)
True
>>> isinstance(iter((1,2,3)), Sequence)
False

这不适用于套装:

>>> isinstance({1,2,3}, Sequence)
False

如果要包含集和映射,请使用collections.abs.Setcollections.abc.Mapping

>>> isinstance({1,2,3}, (Sequence, Set, Mapping))
True

您可能想要创建一个辅助函数,在需要时将迭代转换为序列:

def sequencify(iterable):
    if isinstance(iterable, (Sequence, Set, Mapping)):
        return iterable
    return list(iterable)

现在你可以这样做:

def a_function(iterable):
    iterable = sequencify(iterable)

    for x in iterable:
        print(x)
    for x in iterable:
        print(x)

更简单的替代方法是检查iterable参数是否有__next__方法:

>>> hasattr([1,2,3], '__next__')
False
>>> hasattr(iter([1,2,3]), '__next__')
True

这是有效的,因为良好实现的容器只是迭代而不是迭代器本身,因此它们只有一个__iter__方法返回一个迭代器,该迭代器具有推进迭代的__next__方法。

这将导致:

def sequencify(iterable):
    if not hasattr(iterable, '__next__'):
        return iterable
    return list(iterable)

最后的选择是最简单的:将参数记录为序列 iterable 并让用户负责提供正确的类型。

答案 1 :(得分:0)

您可以检查对象是否具有__next__()方法(Python 3)或next()方法(Python 2)。如果确实如此,那么您可能会认为它实现了迭代器协议,因此其值不可重用。

>>> l = [1, 2, 3, 4]
>>> it = iter(l)
>>> hasattr(l, '__next__')
False
>>> hasattr(it, '__next__')
True

如果有__next__属性,则应检查它是否可调用:

>>> hasattr(l, '__next__') and callable(l.__next__)
False
>>> hasattr(it, '__next__') and callable(it.__next__)
True