Хотя все итерируемые объекты должны быть подклассами. И, к сожалению, не все из них делают.Вот ответ, основанный на том, какой интерфейс реализуют объекты, а не на том, что они «объявляют».
Краткий ответ:
"контейнер", как вы его называете, то есть список / кортеж, который можно повторять более одного раза, в отличие от генератора, которыйбудет исчерпан, как правило, реализует как __iter__
, так и __getitem__
.Следовательно, вы можете сделать это:
>>> def is_container_iterable(o):
... return hasattr(o, '__iter__') and hasattr(o, '__getitem__')
...
>>> is_container_iterable([])
True
>>> is_container_iterable(())
True
>>> is_container_iterable({})
True
>>> is_container_iterable(range(5))
True
>>> is_container_iterable(iter([]))
False
Длинный ответ:
Однако вы можете сделать итерацию, которая не будет исчерпана и не будет поддерживать getitem.Например, функция, которая генерирует простые числа.Вы можете повторить генерацию много раз, если хотите, но наличие функции для получения 1065-го простого числа потребует значительных вычислений, поэтому вы можете не захотеть поддерживать это.: -)
Так есть ли более "надежный" способ?
Ну, все итерируемые будут реализовывать функцию __iter__
, которая будет возвращать итератор.Итераторы будут иметь функцию __next__
.Это то, что используется при итерации по нему.Повторный вызов __next__
в конечном итоге приведет к исчерпанию итератора.
Так что, если он имеет функцию __next__
, он является итератором и будет исчерпан.
>>> def foo():
... for x in range(5):
... yield x
...
>>> f = foo()
>>> f.__next__
<method-wrapper '__next__' of generator object at 0xb73c02d4>
Итерации, которые являютсяеще итераторы не будут иметь функцию __next__
, но будут реализовывать функцию __iter__
, которая будет возвращать итерацию:
>>> r = range(5)
>>> r.__next__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'range' object has no attribute '__next__'
>>> ri = iter(r)
>>> ri.__next__
<method-wrapper '__next__' of range_iterator object at 0xb73bef80>
Таким образом, вы можете проверить, что объект имеет __iter__
, но онне имеет __next__
.
>>> def is_container_iterable(o):
... return hasattr(o, '__iter__') and not hasattr(o, '__next__')
...
>>> is_container_iterable(())
True
>>> is_container_iterable([])
True
>>> is_container_iterable({})
True
>>> is_container_iterable(range(5))
True
>>> is_container_iterable(iter(range(5)))
False
Итераторы также имеют функцию __iter__
, которая будет возвращать себя.
>>> iter(f) is f
True
>>> iter(r) is r
False
>>> iter(ri) is ri
True
Следовательно, вы можете выполнить следующие варианты проверки:
>>> def is_container_iterable(o):
... return iter(o) is not o
...
>>> is_container_iterable([])
True
>>> is_container_iterable(())
True
>>> is_container_iterable({})
True
>>> is_container_iterable(range(5))
True
>>> is_container_iterable(iter([]))
False
Это не получится, если вы реализуете объект, который возвращает сломанный итератор, тот, который не возвращает self, когда вы снова вызываете iter ().Но тогда ваш код (или модули сторонних производителей) на самом деле делает что-то не так.
Это зависит от создания итератора и, следовательно, от вызова объектов __iter__
, что в теории может иметь побочные эффектыв то время как вышеупомянутые вызовы hasattr не должны иметь побочных эффектов.Итак, он вызывает getattribute , который мог бы иметь.Но вы можете исправить это следующим образом:
>>> def is_container_iterable(o):
... try:
... object.__getattribute__(o, '__iter__')
... except AttributeError:
... return False
... try:
... object.__getattribute__(o, '__next__')
... except AttributeError:
... return True
... return False
...
>>> is_container_iterable([])
True
>>> is_container_iterable(())
True
>>> is_container_iterable({})
True
>>> is_container_iterable(range(5))
True
>>> is_container_iterable(iter(range(5)))
False
Это достаточно безопасно и должно работать во всех случаях, кроме случаев, когда объект генерирует __next__
или __iter__
динамически при __getattribute__
вызовах, но если высделай это, ты безумен: -)
Инстинктивно моя предпочтительная версия была бы iter(o) is o
, но мне никогда не нужно было это делать, так что это не основано на опыте.