Как определить разницу между итератором и итератором? - PullRequest
13 голосов
/ 02 апреля 2009

В Python интерфейс итерируемого является подмножеством интерфейса итератора . Это имеет то преимущество, что во многих случаях к ним можно относиться одинаково. Однако между ними существует важное семантическое различие, поскольку для итерируемого __iter__ возвращается новый объект итератора, а не просто self. Как я могу проверить, что итеративный действительно итеративный, а не итератор? Концептуально я понимаю, что итерации являются коллекциями, в то время как итератор управляет только итерацией (т.е. отслеживает позицию), но не является самой коллекцией.

Разница, например, важна, когда нужно сделать цикл несколько раз. Если указан итератор, то второй цикл не будет работать, так как итератор уже использовался и напрямую вызывает StopIteration.

Заманчиво проверить метод next, но это кажется опасным и каким-то неправильным. Должен ли я просто проверить, что второй цикл пуст?

Есть ли способ сделать такой тест более питоническим способом? Я знаю, что это звучит как классический случай LBYL против EAFP, так что, может быть, я должен просто сдаться? Или я что-то упустил?

Edit: С.Лотт говорит в своем ответе ниже, что это, прежде всего, проблема желания сделать несколько проходов через итератор, и что не следует делать это в первую очередь. Однако в моем случае данные очень велики, и в зависимости от ситуации их необходимо многократно передавать для обработки данных (абсолютно невозможно обойти это).

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

Редактировать 2: На самом деле это очень хороший пример для Abstract Base Classes . Методы __iter__ в итераторе и итерации имеют одно и то же имя, но отличаются друг от друга! Так что hasattr бесполезен, но isinstance обеспечивает чистое решение.

Ответы [ 4 ]

13 голосов
/ 02 апреля 2009
'iterator' if obj is iter(obj) else 'iterable'
4 голосов
/ 02 апреля 2009

Тем не менее, существует важное семантическое различие между этими двумя ...

Не совсем смыслово или важно. Они оба повторяемы - они оба работают с оператором for.

Разница, например, важна, когда нужно многократно повторять цикл.

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

Например, допустим, вы обрабатываете список. Вы можете перебирать список, сколько хотите. Почему вы запутались с итератором вместо итерируемого? Хорошо, это не сработало.

Хорошо, вот один. Вы читаете файл в два прохода, и вам нужно знать, как сбросить итерацию. В данном случае это файл, и seek требуется; или закрыть и открыть заново. Это кажется неприглядным. Вы можете readlines получить список, который позволяет два прохода без сложности. Так что в этом нет необходимости.

Подождите, что если у нас будет такой большой файл, что мы не сможем прочитать все это в память? И по непонятным причинам мы тоже не можем искать. Что тогда?

Теперь мы дошли до мельчайших двух проходов. На первом проходе мы что-то накопили. Индекс или резюме или что-то. Индекс содержит все данные файла. Резюме часто представляет собой реструктуризацию данных. С небольшим изменением от «сводки» до «реструктуризации» мы сохранили данные файла в новой структуре. В обоих случаях нам не нужен файл - мы можем использовать индекс или сводку.

Все «двухпроходные» алгоритмы могут быть заменены на один проход исходного итератора или итерируемый и второй проход другой структуры данных.

Это не LYBL или EAFP. Это алгоритм проектирования. Вам не нужно сбрасывать итератор - YAGNI.


Редактировать

Вот пример итератора / итерируемой проблемы. Это просто плохо разработанный алгоритм.

it = iter(xrange(3))
for i in it: print i,; #prints 1,2,3 
for i in it: print i,; #prints nothing

Это тривиально исправлено.

it = range(3)
for i in it: print i
for i in it: print i

«Несколько раз параллельно» тривиально исправлено. Напишите API, который требует итерируемого. И когда кто-то отказывается читать документацию API или отказывается следовать ей после прочтения, его вещи ломаются. Как и должно быть.

«Приятно защищать от случая, когда пользователь предоставляет только итератор, когда требуется несколько проходов», - оба примера безумных людей, пишущих код, нарушающий наш простой API.

Если кто-то достаточно безумен, чтобы прочитать больше всего (но не все документы по API) и предоставить итератор, когда итерация была требуется , вам нужно найти этого человека и научить его (1), как прочитайте всю документацию по API и (2) следуйте документации по API.

Проблема "защиты" не очень реалистична. Эти сумасшедшие программисты замечательно редки. И в тех немногих случаях, когда это происходит, вы знаете, кто они и могут им помочь.


Редактировать 2

Алгоритмы «мы должны читать одну и ту же структуру несколько раз» являются фундаментальной проблемой.

Не делай этого.

for element in someBigIterable:
    function1( element )
for element in someBigIterable:
    function2( element )
...

Сделайте это вместо этого.

for element in someBigIterable:
    function1( element )
    function2( element )
    ...

Или рассмотрим что-то вроде этого.

for element in someBigIterable:
    for f in ( function1, function2, function3, ... ):
        f( element )

В большинстве случаев такого рода «стержень» ваших алгоритмов приводит к программе, которую легче оптимизировать и которая может привести к чистому улучшению производительности.

2 голосов
/ 03 апреля 2009
import itertools

def process(iterable):
    work_iter, backup_iter= itertools.tee(iterable)

    for item in work_iter:
        # bla bla
        if need_to_startover():
            for another_item in backup_iter:

Эта чертова машина времени, которую Рэймонд позаимствовал у Гвидо…

0 голосов
/ 02 апреля 2009

Из-за утки Python,

Любой объект является итеративным, если он определяет метод next() и __iter__() возвращает себя.

Если сам объект не имеет метода next(), __iter__() может вернуть любой объект, у которого есть метод next()

Вы можете обратиться к этому вопросу, чтобы увидеть Итеративность в Python

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...