Подтвердить что ты не робот

Обнаружение, если итератор будет потреблен

Существует ли единый способ узнать, будет ли итерируемый объект итерацией?

Предположим, что у вас есть определенная функция 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)

Но все же там будет 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)

Есть ли решение этой проблемы?

4b9b3361

Ответ 1

Другим, дополнительным вариантом может быть запрос, если iterable является его собственным итератором:

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

потому что в этом случае это просто итератор.

Это работает с генераторами, итераторами, файлами и многими другими объектами, которые предназначены для "одного запуска", другими словами, все итерации, которые являются итераторами сами по себе, потому что itater возвращает 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() поднимает StopIteration, он будет продолжать делать это при последующих вызовах. Реализации, которые не подчиняются этому свойству, считаются нарушенными. (Это ограничение было добавлено в Python 2.3, в Python 2.2 различные итераторы разбиты в соответствии с этим правилом.)

Таким образом, в этом случае объект должен был бы возвращать итератор, отличный от самого себя, несмотря на то, что он сам себя поглощал. В этом случае это можно сделать с помощью

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

который возвращает новую генерацию на каждой итерации - что-то, что будет падать, если затор в случае, который мы обсуждаем.

Ответ 2

Одна из возможностей - проверить, является ли элемент последовательностью, используя isinstance(val, collections.Sequence). Недостаточная потребляемость по-прежнему не полностью гарантирована, но я думаю, что это лучшее, что вы можете получить. Последовательность Python должна иметь длину, что означает, что, по крайней мере, она не может быть итератором с открытым концом и, как правило, подразумевает, что элементы должны быть известны заранее, что, в свою очередь, означает, что их можно повторить не потребляя их. По-прежнему можно писать патологические классы, которые соответствуют протоколу последовательности, но не повторяются, но вы никогда не сможете справиться с ними.

Обратите внимание, что ни один из Iterable и Iterator не является подходящим выбором, поскольку эти типы не гарантируют длину и, следовательно, не могут гарантировать, что итерация будет даже конечной, не говоря уже о повторяемости. Однако вы можете проверить как Sized, так и Iterable.

Важно отметить, что ваша функция будет дважды перебирать свой аргумент, предупреждая пользователей, что они должны передать объект, который поддерживает это.

Ответ 3

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

    for v in vals1:
        chomp(v)

    for v in vals2:
        yum(v)

В этом случае tee закончит сохранение внутренней целостности vals внутри, так как один итератор будет завершен до запуска другого.

Ответ 4

Многие ответы приблизились к точке, но пропустите ее.

Iterator - это объект, который потребляется путем итерации по нему. Об этом нет. Пример объектов итератора - это те, которые возвращаются вызовами iter() или те, которые возвращаются функциями itertools module.

Правильный способ проверить, является ли объект итератором, - это вызвать 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.

Протокол итератора требует, чтобы объекты итератора возвращались как их собственный итератор (и, следовательно, были итерабельными). Это не требуется для работы итерационной механики, но вы не сможете зацикливать объект итератора.

Все это говорит о том, что вам нужно сделать, это проверить, предоставлен ли вам Итератор, и если это произойдет, сделайте копию результата итерации (с помощью list()). Ваш isconsumable(obj) (как уже говорилось) isinstance(obj, Iterator).

Обратите внимание, что это также работает для xrange(). xrange(10) возвращает объект xrange. Каждый раз, когда вы используете объекты xrange, он возвращает новый итератор, начиная с самого начала, поэтому вы в порядке и не нуждаетесь в создании копии.

Ответ 5

Вот сводка определений.

контейнер

  • Объект с методом __contains__

Генератор

  • Функция, которая возвращает итератор.

итерация

  • Объект с методом __iter__() или __getitem__().
  • Примеры итераций включают все типы последовательностей (например, список, str и кортеж) и некоторые типы без последовательности, такие как dict и file.
  • Когда итерируемый объект передается как аргумент встроенной function iter(), он возвращает итератор для объекта. Эта итератор хорош для одного прохода над набором значений.

итератора

  • Итерируемый, который имеет метод next().
  • Итераторы должны иметь   __iter__(), который возвращает сам объект итератора.
  • Итератором является  хорошо для одного прохода над набором значений.

последовательность

  • Итерируемый, который поддерживает эффективный доступ к элементу с помощью integer индексы  с помощью специального метода __getitem__() и определяет метод len(), который возвращает  длина последовательности.
  • Некоторые встроенные типы последовательности list, str,  tuple и unicode.
  • Обратите внимание, что 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()