Что именно означает итерация в Python? Почему мой объект, реализующий __getitem __ (), не является итерируемым?

Сначала я хочу уточнить, я НЕ спрашиваю, что такое «итератор».

Вот как термин «итерабельность» определяется в документе Python:

повторяемый

Объект, способный возвращать свои члены по одному. Примеры итераций включают все типы последовательностей (такие как list, str и tuple), а также некоторые непоследовательные типы, такие как dict, файловые объекты и объекты. любых классов, которые вы определяете с помощью метода __iter __ () или __getitem __ ().

Итерации можно использовать в цикле for и во многих других местах, где требуется последовательность (zip (), map (), ...). Когда итерируемый объект передается в качестве аргумента встроенной функции iter (), она возвращает итератор для объекта. Этот итератор подходит для одного прохода по набору значений. При использовании итераторов обычно нет необходимости вызывать iter () или самостоятельно работать с объектами итератора. Оператор for делает это автоматически, создавая временную безымянную переменную для хранения итератора на время цикла.

См. Также итератор, последовательность и генератор.

Как предложили другие люди, использование isinstance(e, collections.Iterable) - это самый питонический способ проверить, является ли объект итерируемым.
Итак, я провел несколько тестов с Python 3.4.3:

from collections.abc import Iterable

class MyTrain:
    def __getitem__(self, index):
        if index > 3:
            raise IndexError("that's enough!")

        return index

for name in MyTrain():
    print(name)  # 0, 1, 2, 3

print(isinstance(MyTrain(), Iterable))  # False

Результат довольно странный: MyTrain определил __getitem__ метод, но он не считается итерируемым объектом, не говоря уже о том, что он способен возвращать по одному числу за раз.

Затем я удалил __getitem__ и добавил метод __iter__:

from collections.abc import Iterable

class MyTrain:    
    def __iter__(self):
        print("__iter__ called")
        pass

print(isinstance(MyTrain(), Iterable))  # True

for name in MyTrain():
    print(name)  # TypeError: iter() returned non-iterator of type 'NoneType'

Теперь он считается «истинным» итеративным объектом, несмотря на то, что он ничего не может произвести во время итерации.

Так я что-то неправильно понял или документация неверна?


person laike9m    schedule 26.09.2015    source источник
comment
isinstance не будет проверять, что интерфейс реализован правильно, это не выяснится до тех пор, пока вы не попытаетесь его перебрать, а только то, что соответствующий метод (ы) (в данном случае только __iter__).   -  person jonrsharpe    schedule 26.09.2015
comment
использование isinstance(e, collections.Iterable) - это самый питонический способ проверить, является ли объект итерируемым - нет, я бы сказал, что попытка перебрать его - это самый питонический способ!   -  person jonrsharpe    schedule 26.09.2015
comment
У Джонршарпа это есть. Проще просить прощения, чем разрешения.   -  person Nick Bailey    schedule 26.09.2015
comment
чтобы проверить, является ли что-то итерируемым, я делаю блок try / except, где я пытаюсь var = iter (var), если он выдает и исключение, то это не повторяемый   -  person DevLounge    schedule 26.09.2015
comment
@jonrsharpe. Но это может быть обреченным на провал, поскольку может потреблять итеративное. Возможно, лучше было бы попробовать iter(x) и посмотреть, не повысится ли он TypeError.   -  person ekhumoro    schedule 26.09.2015
comment
@ekhumoro, но если он повторяется, тогда вы хотите использовать его, а если нет, то вы не можете. Трудно понять, как это могло зайти слишком далеко не так.   -  person jonrsharpe    schedule 26.09.2015
comment
@jonrsharpe Тогда что такое isinstance(e, collections.Iterable)?   -  person laike9m    schedule 26.09.2015
comment
@ laike9m что ты имеешь в виду о чем [это] все?   -  person jonrsharpe    schedule 26.09.2015
comment
@jonrsharpe Для чего вы это будете использовать?   -  person laike9m    schedule 26.09.2015
comment
@jonrsharpe. Это пойдет не так, если вам нужно выяснить, является ли объект итерируемым, но без каких-либо побочных эффектов (например, его потребления).   -  person ekhumoro    schedule 26.09.2015
comment
@ekhumoro, я хочу сказать, что если вы используете утиную печать, вы никогда не сможете узнать, является ли объект итерируемым , кроме как (пытаясь) перебирать его. Не похоже, чтобы вы сделали if is_iterable(thing): for whatever in thing:, а затем реализовали is_iterable также с for whatever in thing:!   -  person jonrsharpe    schedule 26.09.2015
comment
@ laike9m Я не уверен, что вы захотите использовать его; как я уже комментировал, способ Pythonic узнать, можно ли что-то итерировать, - это попытаться перебрать это.   -  person jonrsharpe    schedule 26.09.2015
comment
@jonrsharpe. Конечно, но есть и другие причины для проверки без потребления. Например, вашему коду может потребоваться выполнить некоторые действия по настройке, которые условно зависят от того, является ли объект повторяемым или нет. Использование iter на самом деле то же самое, что просто попытаться перебрать его, но с дополнительным преимуществом, заключающимся в том, что итератор не используется первым.   -  person ekhumoro    schedule 26.09.2015
comment
@ekhumoro достаточно честно; хотя я никогда не попадал в такую ​​ситуацию, вы правы в том, что использование iter позволит вам проверить в этих обстоятельствах.   -  person jonrsharpe    schedule 26.09.2015


Ответы (3)


Я думаю, что путаница здесь заключается в том, что, хотя реализация __getitem__ действительно позволяет вам перебирать объект, она не является частью интерфейса, определенного _ 2_.

абстрактные базовые классы позволяют создавать виртуальные подклассы, где классы, реализующие указанные методы (в случае Iterable, только __iter__), рассматриваются isinstance и issubclass как подклассы ABC , даже если они явно не наследуются от них. Однако он не проверяет, действительно ли работает реализация метода, просто предоставлена ​​она или нет.

Для получения дополнительной информации см. PEP-3119, в котором представлены азбуки.


использование isinstance(e, collections.Iterable) - это самый питонический способ проверить, является ли объект итерируемым

Я не согласен; Я бы использовал утиный ввод и просто попытался перебрать объект. Если объект не итерируемый, будет поднят TypeError, который вы можете поймать в своей функции, если хотите иметь дело с не повторяемыми входами, или разрешить перенаправление до вызывающего, если нет. Это полностью обходится стороной, как объект решил реализовать итерацию, и просто выясняет, выполняется ли это в наиболее подходящее время.


Чтобы добавить еще немного, я считаю, что процитированные вами документы слегка вводят в заблуждение. Процитирую iter docs, которые, возможно, проясняют это:

object должен быть объектом коллекции, который поддерживает протокол итерации (метод __iter__()), или он должен поддерживать протокол последовательности (метод __getitem__() с целочисленными аргументами, начинающимися с 0).

Это проясняет, что, хотя оба протокола делают объект итерируемым, только один является фактическим «протоколом итерации», и именно его isinstance(thing, Iterable) проверяет. Таким образом, мы можем сделать вывод, что один из способов проверить «вещи, которые можно перебирать» в наиболее общем случае:

isinstance(thing, (Iterable, Sequence))

хотя для этого также требуется реализовать __len__ вместе с __getitem__ в «виртуальный подкласс» Sequence.

person jonrsharpe    schedule 26.09.2015
comment
Спасибо, jorsharpe, ваш ответ очень помогает. На данный момент я действительно думаю, что процитированный мной документ вводит в заблуждение. Повторение глоссария в документе глоссария означает, что его можно повторять независимо от того, как это делается, в то время как collections.abc.Iterable в Python представляет действительный контейнер итерации (я придумал это слово), то есть любой объект, который определяет метод __iter__. - person laike9m; 27.09.2015
comment
Таким образом, collections.abc.Iterable не обязательно повторяется, поскольку он может вернуть недопустимый итератор, который не реализует __next__. Между тем, итерируемый объект не может иметь никакого отношения к collections.abc.Iterable, только определяя __getitem__. Что вы думаете? - person laike9m; 27.09.2015

Это итерация. Однако вы не унаследовали от abc.Iterable, поэтому, естественно, Python не будет сообщать, что он унаследован от этого класса. Эти две вещи - быть итерируемым и происходить от этого базового класса - совершенно разные.

person Daniel Roseman    schedule 26.09.2015
comment
Я думаю, что OP смущает то, что классы, реализующие __iter__, являются виртуальными подклассами Iterable, даже если они не наследуют его явно. - person jonrsharpe; 26.09.2015

Iterable - это что-то (коллекция чего угодно), допускающее некоторую итерацию по своим элементам. Но каков общий способ итерации в Python? Это использование ключевого слова - in, которое использует __iter__ метод объекта. Таким образом, в этих терминах любой объект, который определяет __iter__, может использоваться с in и является Iterable.

Итак, наиболее «утиный» способ проверить, является ли объект итерируемым, - это если объект такой (Да, я неявно знаю, что происходит и в случае isinstance из-за виртуальных классов)

hasattr(train, '__iter__')

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

Если у вас есть ошибочная реализация __iter__, что не означает, что объект не является итерируемым, это просто означает, что в вашем коде есть ошибка.

Примечание: - Те объекты, которые не определяют __iter__, могут быть итерируемыми в общем смысле, используя какой-либо другой метод, просто они не могут использоваться с ключевым словом in. Например: - экземпляр NumberList может быть повторен по методу each, но не повторяется в смысле Python.

class NumberList:

     def __init__(self, values):
         self.values = values

     def each(self):
         return self.values
person hspandher    schedule 26.09.2015
comment
1. Это не утка. 2. hasattr(train, '__iter__') примерно то, что isinstance(train, Iterable) уже делает. - person jonrsharpe; 26.09.2015
comment
@jonrshare Я знаю, но это немного вводит в заблуждение. Вам не кажется, что isinstance должен вместо этого проверять наследование? - person hspandher; 26.09.2015
comment
Если объект предоставляет метод iter, нас не волнует, является ли он экземпляром Iterable или нет. Разве это не то, что означает утиная печать? - person hspandher; 26.09.2015
comment
Вам не кажется, что isinstance должен вместо этого проверять наследование - нет, я думаю, он должен делать то, что задокументировано: Возвращает истину, если аргумент объекта является экземпляром аргумента classinfo или его подкласса (прямого, косвенного или виртуального). (где для виртуальных см. docs.python.org/3/glossary .html # term-abstract-base-class). - person jonrsharpe; 26.09.2015
comment
Утиный ввод означает, что hasattr вообще не используется, а просто пишется for thing in train: и либо устранение ошибки в функции, либо разрешение вызывающей стороне ее уловить. - person jonrsharpe; 26.09.2015
comment
Вы говорите, что если я определяю «iter» для некоторого метода, python неявно считает его наследованием от абстрактного класса, тем самым делая его экземпляром Iterable. Итак, то, что делает isinstance, почти то же самое, что и проверка hasattr. Вам не кажется, что это противоречит явному лучше, чем неявному. - person hspandher; 26.09.2015
comment
Если бы он использовал in, у нас не было бы этого обсуждения. Все, что я говорю, проверка hasattr выглядит лучше, чем проверка экземпляра с точки зрения динамического языка - person hspandher; 26.09.2015