2009-04-02 4 views
12

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

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

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

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

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

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

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

ответ

13
'iterator' if obj is iter(obj) else 'iterable' 
+0

Вау, это, кажется, ответ, который я искал, спасибо! Я подожду немного, прежде чем принимать его, если кто-то может указать на проблему. – nikow

+0

Ну, проблема в том, что один «пустой» вызов obj .__ iter __(), но я не вижу другого надежного способа сделать это. – vartec

+1

Хотя я не знаю контр-пример, это не * гарантировано * для работы. – tzot

4

Однако есть важное смысловое различие между этими двумя ...

Не совсем семантический или важным. Они оба итерабельны - они оба работают с инструкцией for.

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

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

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

Хорошо, вот один. Вы читаете файл за два прохода, и вам нужно знать, как восстановить итерируемый. В этом случае это файл, и требуется 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.

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


Edit 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) 

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

+0

Как насчет нескольких раз параллельно? Например. несколько потоков, повторяющихся в одной коллекции? Или даже один поток, такой как легкомысленная наивная реализация «делает ли этот сборник одним и тем же элементом дважды?». – Edmund

+0

Спасибо, я добавил объяснение к вопросу. У вас есть верный момент, но в моем случае я верю, что это не работает. – nikow

+0

"замечательно редкость". Я бы не согласился, программисты, которые не могут сказать iterable с итератора, ни в коем случае не редки. «Вы знаете, кто они и могут им помочь». Обычно это не ваша работа, а в корпорации «помогать им» не очень хорошо воспринимается, особенно если это другой отдел. – vartec

0

Из-за уткой набрав Пайтона,

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

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

Вы можете передать этот вопрос, чтобы увидеть Iterability in Python

+0

Попробуйте следующее: класс A (объект): def __iter __ (self): return iter ([1,2,3]) def next (self): yield 7 – vartec

+0

На самом деле это проблема утиного ввода: он может скрыть семантический/концептуальная разница. Это позволяет нам писать для i в диапазоне (3) вместо i в iter (диапазон (3)), но может вызвать тонкие проблемы. – nikow

+0

Простите, я точно не понял? Что-то не так? –

2
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: 

эту чертову машину времени, которая Раймонд заимствован из Гвидо ...