13

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

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

def crunch (vals):

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

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

如果使用不是列表的可迭代对象调用函数,则会出现问题。在以下调用中,该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)

但是仍然存在调用代码将数据存储在某些东西中的情况

  • 不能消费
  • 不是列表

例如:

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)

这个问题有解决方案吗?

4

5 回答 5

6

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

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

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

于 2013-03-13T08:21:37.080 回答
5

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

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

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

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

你称之为

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

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

该协议的意图是一旦迭代器的next()方法 raise 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 下降。

于 2013-03-13T08:27:53.923 回答
4
def crunch (vals):
    vals1, vals2 = itertools.tee(vals, 2)

    for v in vals1:
        chomp(v)

    for v in vals2:
        yum(v)

在这种情况下,由于一个迭代器在另一个迭代器启动之前完成,tee最终将在内部存储整个vals

于 2013-03-13T08:21:24.660 回答
3

许多答案都接近重点,但错过了它。

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

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

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

# 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对象。当您调用iter()可迭代对象时,它将返回一个迭代器。在这个页面上评论我犯了一个错误,所以我会在这里澄清。可迭代对象不需要在每次调用时都返回一个新的迭代器。许多迭代器本身是可迭代的(即您可以调用iter()它们)并且它们将返回对象本身。

一个简单的例子是列表迭代器。iter(my_list)并且iter(iter(my_list))是同一个对象,这基本上是@glglgl answer正在检查的内容。

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

综上所述,您应该做的是检查您是否获得了迭代器,如果是这样,请复制迭代结果(使用list())。你isconsumable(obj)是(正如有人已经说过的那样)isinstance(obj, Iterator)

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

于 2013-03-13T09:53:35.647 回答
1

这是定义的摘要

容器

  • 带有__contains__方法的对象

发电机

  • 返回迭代器的函数。

可迭代的

  • 带有__iter__()or__getitem__()方法的对象。
  • 可迭代的示例包括所有序列类型(如 list、str 和 tuple)和一些非序列类型,如 dict 和 file。
  • 当可迭代对象作为参数传递给内置函数iter()时,它会返回该对象的迭代器。此迭代器适用于遍历一组值。

迭代器

  • 具有next()方法的可迭代对象。
  • 迭代器需要有一个 __iter__()返回迭代器对象本身的方法。
  • 迭代器适用于遍历一组值。

顺序

  • __getitem__()一个迭代,它通过特殊方法支持使用整数索引进行有效的元素访问,并定义了一个len()返回序列长度的方法。
  • 一些内置的序列类型是liststrtupleunicode
  • 请注意, dict 也支持__getitem__()and __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()
于 2013-03-13T17:28:36.233 回答