检测是否将使用迭代器

时间:2013-03-13 08:13:24

标签: python

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

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

def crunch (vals):

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

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

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

crunch(iter(range(4))

我们原则上可以通过重新定义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)

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

  • 无法消费
  • 不是列表

例如:

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)

这个问题有解决方案吗?

5 个答案:

答案 0 :(得分:6)

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

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

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

答案 1 :(得分:5)

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

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

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

这适用于生成器,迭代器,文件和许多其他为“一次运行”设计的对象,换句话说,所有迭代器本身都是迭代器,因为iterator returns self from its __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)

你称之为

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

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

  

协议的目的是一旦迭代器的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

在每次迭代时返回一个新的生成器 - 在我们讨论的情况下,这些东西会掉落在mash中。

答案 2 :(得分:4)

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

    for v in vals1:
        chomp(v)

    for v in vals2:
        yum(v)

在这种情况下,tee将在内部存储整个vals,因为一次迭代在另一次迭代开始之前完成

答案 3 :(得分:3)

许多答案接近这一点但却错过了。

Iterator 是通过迭代消耗的对象。没有其他办法了。迭代器对象的示例是通过调用iter()返回的对象,或itertools module中函数返回的对象。

检查对象是否为迭代器的正确方法是调用isinstance(obj, Iterator)。这基本上检查对象是否实现了next()方法(Python 3中的__next__()),但您不需要关心它。

所以,请记住,迭代器总是被消耗掉。例如:

# suppose you have a list
my_list = [10, 20, 30]
# and build an iterator on the list
my_iterator = iter(my_list)
# iterate the first time over the object
for x in my_iterator:
    print x
# then again
for x in my_iterator:
    print x

这将打印列表内容一次

然后有Iterable个对象。当你在一个iterable上调用iter()时,它将返回一个迭代器。在这个页面评论我犯了一个错误,所以我将在这里澄清。每次调用都不需要可重试对象来返回 new iterator 。许多迭代器本身都是可迭代的(即你可以在它们上面调用iter())并且它们将返回对象本身。

一个简单的例子就是列表迭代器。 iter(my_list)iter(iter(my_list))是同一个对象,这基本上是@glglgl的答案。

iterator protocol要求迭代器对象将自己作为自己的迭代器返回(因此可以迭代)。这不是迭代机制工作所必需的,但是你无法遍历迭代器对象。

所有这些都说,你应该做的是检查你是否得到了迭代器,如果是这种情况,请复制迭代结果(list())。您的isconsumable(obj)是(正如有人已经说过的)isinstance(obj, Iterator)

请注意,这也适用于xrange()xrange(10)会返回xrange个对象。每次你对xrange对象进行操作时,它都会从头开始返回一个新的迭代器,所以你很好,不需要复制。

答案 4 :(得分:1)

这是summary of definitions

容器

  • 具有__contains__方法
  • 的对象

发生器

  • 返回迭代器的函数。

迭代

  • 具有__iter__()__getitem__()方法的对象。
  • 可迭代的例子包括所有序列类型(例如列表, str和tuple)以及一些非序列类型,如dict和file。
  • 当可迭代对象作为参数传递给内置函数时 函数iter(),它返回对象的迭代器。这个 迭代器适用于一组值的传递。

迭代

  • 具有next()方法的可迭代。
  • 迭代器必须有一个    返回迭代器对象本身的__iter__()方法。
  • 迭代器是    适用于对一组价值观的一次通过。

序列

  • 一个可迭代的,它支持使用整数进行有效的元素访问 指数    通过__getitem__()特殊方法定义返回的len()方法    序列的长度。
  • 一些内置序列类型为liststr,    tupleunicode
  • 请注意,dict还支持__getitem__()和    __len__(),但被认为是映射而不是序列,因为    查找使用任意不可变键而不是整数。

现在有很多方法可以测试对象是可迭代的,迭代器还是某种序列。以下是这些方法的摘要,以及它们如何对各种对象进行分类:

               Iterable Iterator iter_is_self Sequence MutableSeq
object                                                           
[]                 True    False        False     True       True
()                 True    False        False     True      False
set([])            True    False        False    False      False
{}                 True    False        False    False      False
deque([])          True    False        False    False      False
<listiterator>     True     True         True    False      False
<generator>        True     True         True    False      False
string             True    False        False     True      False
unicode            True    False        False     True      False
<open>             True     True         True    False      False
xrange(1)          True    False        False     True      False
Foo.__iter__       True    False        False    False      False

                Sized has_len has_iter has_contains
object                                             
[]               True    True     True         True
()               True    True     True         True
set([])          True    True     True         True
{}               True    True     True         True
deque([])        True    True     True        False
<listiterator>  False   False     True        False
<generator>     False   False     True        False
string           True    True    False         True
unicode          True    True    False         True
<open>          False   False     True        False
xrange(1)        True    True     True        False
Foo.__iter__    False   False     True        False

每列引用一种不同的方式来分类迭代,每一行引用一种不同的对象。


import pandas as pd
import collections
import os


def col_iterable(obj):
    return isinstance(obj, collections.Iterable)


def col_iterator(obj):
    return isinstance(obj, collections.Iterator)


def col_sequence(obj):
    return isinstance(obj, collections.Sequence)


def col_mutable_sequence(obj):
    return isinstance(obj, collections.MutableSequence)


def col_sized(obj):
    return isinstance(obj, collections.Sized)


def has_len(obj):
    return hasattr(obj, '__len__')


def listtype(obj):
    return isinstance(obj, types.ListType)


def tupletype(obj):
    return isinstance(obj, types.TupleType)


def has_iter(obj):
    "Could this be a way to distinguish basestrings from other iterables?"
    return hasattr(obj, '__iter__')


def has_contains(obj):
    return hasattr(obj, '__contains__')


def iter_is_self(obj):
    "Seems identical to col_iterator"
    return iter(obj) is obj


def gen():
    yield


def short_str(obj):
    text = str(obj)
    if text.startswith('<'):
        text = text.split()[0] + '>'
    return text


def isiterable():
    class Foo(object):
        def __init__(self):
            self.data = [1, 2, 3]

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

        def __repr__(self):
            return "Foo.__iter__"
    filename = 'mytestfile'
    f = open(filename, 'w')
    objs = [list(), tuple(), set(), dict(),
            collections.deque(), iter([]), gen(), 'string', u'unicode',
            f, xrange(1), Foo()]
    tests = [
        (short_str, 'object'),
        (col_iterable, 'Iterable'),
        (col_iterator, 'Iterator'),
        (iter_is_self, 'iter_is_self'),
        (col_sequence, 'Sequence'),
        (col_mutable_sequence, 'MutableSeq'),
        (col_sized, 'Sized'),
        (has_len, 'has_len'),
        (has_iter, 'has_iter'),
        (has_contains, 'has_contains'),
    ]
    funcs, labels = zip(*tests)
    data = [[test(obj) for test in funcs] for obj in objs]
    f.close()
    os.unlink(filename)
    df = pd.DataFrame(data, columns=labels)
    df = df.set_index('object')
    print(df.ix[:, 'Iterable':'MutableSeq'])
    print
    print(df.ix[:, 'Sized':])

isiterable()