В Python, как я могу определить, если объект является итерируемым?

есть ли такой метод, как isiterable? Единственное решение, которое я нашел до сих пор, - это вызвать

hasattr(myObj, '__iter__')

но я не уверен,насколько это доказательство.

19 ответов


  1. проверка __iter__ работает на типах последовательностей, но это приведет к сбою, например, strings в Python 2. Я также хотел бы знать правильный ответ, до тех пор, вот одна возможность (которая будет работать и на строках):

    try:
        some_object_iterator = iter(some_object)
    except TypeError as te:
        print some_object, 'is not iterable'
    

    на iter встроенные проверки для __iter__ метод или в случае строк __getitem__ метод.

  2. еще одно общее для Python подход-предположить, повторяемое, то не изящно, если это не работает на данном объекте. Глоссарий Python:

    Pythonic стиль программирования, который определяет тип объекта путем проверки его метода или подписи атрибута, а не явным отношением к некоторому объекту типа ("если он выглядит как утка и крякает как утка, должно быть утка.") Подчеркивая интерфейсы, а не конкретные типы, хорошо разработанный код повышает свою гибкость за счет допускает полиморфное замещение. Duck-typing позволяет избежать тестов с использованием type () или isinstance (). вместо этого он обычно использует стиль программирования EAFP (легче попросить прощения, чем разрешения).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
    
  3. на collections модуль предоставляет некоторые абстрактные базовые классы, которые позволяют задавать классы или экземпляры, если они предоставляют определенную функциональность, для пример:

    import collections
    
    if isinstance(e, collections.Iterable):
        # e is iterable
    

    однако это не проверяет классы, которые можно перебирать через __getitem__.


утиной типизацией

try:
    iterator = iter(theElement)
except TypeError:
    # not iterable
else:
    # iterable

# for obj in iterator:
#     pass

тип проверки

использовать Абстрактные Базовые Классы. Им нужен хотя бы Python 2.6 и они работают только для классов нового стиля.

from collections.abc import Iterable   # import directly from collections for Python < 3.3

if isinstance(theElement, Iterable):
    # iterable
else:
    # not iterable
, iter() немного более надежный, как описано документация:

проверка isinstance(obj, Iterable) обнаруживает классов зарегистрирован как Iterable или что есть __iter__() способ, но он не обнаруживает классы, которые итерируют с __getitem__() метод. Единственный надежный способ определить, является ли объект является iterable является вызов iter(obj).


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

try:
    iter(maybe_iterable)
    print('iteration will probably work')
except TypeError:
    print('not iterable')

сначала я перечислю факты, а затем последую за быстрым напоминанием о том, что происходит, когда вы используете for цикл в python, а затем обсуждение, чтобы проиллюстрировать факты.

факты

  1. вы можете получить итератор из любого объекта o по телефону iter(o) если хотя бы одно из следующих условий верно:

    a)o есть __iter__ метод, который возвращает объект iterator. Итератор-это любой объект с __iter__ и __next__ (Python 2:next) метод.

    b)o есть __getitem__ метод.

  2. проверка по пример Iterable или Sequence, или проверка атрибут __iter__ не хватает.

  3. если объект o реализует только __getitem__, но не __iter__, iter(o) построит итератор, который пытается извлечь элементы из o по индексу, начиная с индекса 0. Итератор поймает любое IndexError (но никаких других ошибок), который поднимается, а затем поднимается .

  4. в самом общем смысле, невозможно проверить, возвращен ли итератор iter в здравом уме, кроме как попробовать.

  5. если объект o осуществляет __iter__, the iter функция убеждается что объект возвращен __iter__ - это итератор. Нет вменяемость проверить если объект реализует только __getitem__.

  6. __iter__ выигрывает. Если объект o реализует как __iter__ и __getitem__, iter(o) позвоню __iter__.

  7. если вы хотите сделать свой собственный итерируемые объекты, всегда использовать тег __iter__ метод.

for петли

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

при использовании for item in o для некоторого iterable объекта o, Python вызывает iter(o) и ожидает объект iterator в качестве возвращаемого значения. Итератор-это любой объект, который реализует __next__ (или next в Python 2) Метод и __iter__ метод.

по соглашению __iter__ метод итератора должен возвращать сам объект (т. е. return self). Python, а затем называет next на итераторе пока StopIteration поднимается. Все это происходит неявно, но следующая демонстрация делает его видимым:

import random

class DemoIterable(object):
    def __iter__(self):
        print('__iter__ called')
        return DemoIterator()

class DemoIterator(object):
    def __iter__(self):
        return self

    def __next__(self):
        print('__next__ called')
        r = random.randint(1, 10)
        if r == 5:
            print('raising StopIteration')
            raise StopIteration
        return r

итерация по a DemoIterable:

>>> di = DemoIterable()
>>> for x in di:
...     print(x)
...
__iter__ called
__next__ called
9
__next__ called
8
__next__ called
10
__next__ called
3
__next__ called
10
__next__ called
raising StopIteration

Обсуждение и иллюстрации

в пунктах 1 и 2: Получение итератора и ненадежных проверок

рассмотрим следующий класс:

class BasicIterable(object):
    def __getitem__(self, item):
        if item == 3:
            raise IndexError
        return item

вызов iter С BasicIterable возвращает итератор без каких-либо проблем, потому что BasicIterable осуществляет __getitem__.

>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>

однако, важно отметить, что b нет __iter__ атрибут и не считается экземпляром Iterable или Sequence:

>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False

вот почему Свободно Python Лучано Рамальо рекомендует позвонить iter и обработка потенциала TypeError а самый точный способ проверить, является ли объект является итерируемым. Цитата прямо из книги:

начиная с Python 3.4, самый точный способ проверить, является ли объект x is iterable является вызов iter(x) и решения TypeError исключение, если это не так. Это более точно, чем использование isinstance(x, abc.Iterable) , потому что iter(x) также считает, что наследство __getitem__ метод, в то время как Iterable ABC не.

в пункте 3: итерация по объектам, которые предоставляют только __getitem__, но не __iter__

итерация над экземпляром BasicIterable работает как ожидалось: Python создает итератор, который пытается получить элементы по индексу, начиная с нуля, до IndexError поднимается. Этот демо-объекта __getitem__ метод просто возвращает item который был предоставлен в качестве аргумента __getitem__(self, item) по iterator, возвращенный iter.

>>> b = BasicIterable()
>>> it = iter(b)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

обратите внимание, что итератор вызывает StopIteration когда он не может вернуть следующий элемент и что IndexError, который поднимается на item == 3 обрабатывается внутренне. Вот почему петля над BasicIterable с for цикл работает, как ожидалось:

>>> for x in b:
...     print(x)
...
0
1
2

вот еще один пример, чтобы привести домой концепцию как iterator, возвращенный iter пытается получить доступ к элементам по индексу. WrappedDict не наследуется от dict, что означает, что экземпляры не имеют __iter__ метод.

class WrappedDict(object): # note: no inheritance from dict!
    def __init__(self, dic):
        self._dict = dic

    def __getitem__(self, item):
        try:
            return self._dict[item] # delegate to dict.__getitem__
        except KeyError:
            raise IndexError

обратите внимание, что вызов __getitem__ возложены на dict.__getitem__ для которого квадратная скобка-это просто стенография.

>>> w = WrappedDict({-1: 'not printed',
...                   0: 'hi', 1: 'StackOverflow', 2: '!',
...                   4: 'not printed', 
...                   'x': 'not printed'})
>>> for x in w:
...     print(x)
... 
hi
StackOverflow
!

по пунктам 4 и 5:iter проверяет наличие итератора при вызове __iter__:

, когда iter(o) для объект o, iter убедитесь, что возвращаемое значение __iter__, если метод присутствует, является итератором. Это означает, что возвращаемый объект необходимо реализовать __next__ (или next в Python 2) и __iter__. iter не удается выполнить проверку здравомыслия для объектов, которые только обеспечить __getitem__, потому что он не может проверить, доступны ли элементы объекта по целочисленному индексу.

class FailIterIterable(object):
    def __iter__(self):
        return object() # not an iterator

class FailGetitemIterable(object):
    def __getitem__(self, item):
        raise Exception

обратите внимание, что построение итератора из FailIterIterable экземпляры немедленно терпят неудачу при построении итератора из FailGetItemIterable успешно, но будет выдавать исключение при первом вызове __next__.

>>> fii = FailIterIterable()
>>> iter(fii)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'object'
>>>
>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/iterdemo.py", line 42, in __getitem__
    raise Exception
Exception

по пункту 6:__iter__ победы

это просто. Если объект реализует __iter__ и __getitem__, iter будем называть __iter__. Рассмотрим следующий класс

class IterWinsDemo(object):
    def __iter__(self):
        return iter(['__iter__', 'wins'])

    def __getitem__(self, item):
        return ['__getitem__', 'wins'][item]

и выход при циклическом пример:

>>> iwd = IterWinsDemo()
>>> for x in iwd:
...     print(x)
...
__iter__
wins

в пункте 7: ваши итерационные классы должны реализовать __iter__

вы можете спросить себя, почему большинство встроенных последовательностей, как list реализовать __iter__ метод, когда __getitem__ будет достаточно.

class WrappedList(object): # note: no inheritance from list!
    def __init__(self, lst):
        self._list = lst

    def __getitem__(self, item):
        return self._list[item]

в конце концов, итерация над экземплярами класса выше, который делегирует вызовы __getitem__ to list.__getitem__ (использование квадратных скобок), будет работать отлично:

>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
...     print(x)
... 
A
B
C

причины, по которым ваши пользовательские iterables должны реализовать __iter__ следующим образом:

  1. если вы реализуете __iter__, экземпляры будут считаться iterables и isinstance(o, collections.Iterable) вернутся True.
  2. если объект, возвращенный __iter__ не является итератором,iter сразу и не поднять TypeError.
  3. специальная обработка __getitem__ существует для обратной совместимости. Цитирование опять же из Fluent Python:

именно поэтому любой последовательности Python является итерируемым: все они исполняют __getitem__ . Фактически, стандартные последовательности также реализуют __iter__, и твое тоже, потому что специальная обработка __getitem__ существует для обратной совместимости и может быть ушел в будущее (хотя это не устарело, как я пишу это).


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

в Python хорошей практикой является " попробовать и увидеть "вместо"проверки".


try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

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


лучшее решение я нашел до сих пор:

hasattr(obj, '__contains__')

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

преимущества (ни одно из других решений не имеет всех трех):

  • это выражение (работает как лямда -, а не попробовать...кроме вариант)
  • это (должно быть) реализованы все итераторы, в том числе строки (в отличие от __iter__)
  • работает на любом Python >= 2.5

Примечания:

  • философия Python "просите прощения, а не разрешения" не работает хорошо, когда, например, в списке у вас есть как iterables, так и non-iterables, и вам нужно относиться к каждому элементу по-разному в зависимости от его типа (обработка iterables на try и non-iterables на except б работать, но это будет выглядеть уродливее и заблуждение)
  • решения этой проблемы, которые пытаются фактически перебирать объект (например, [x для x в obj]), чтобы проверить, является ли он итерационным, могут вызвать значительные штрафы за производительность для больших итераций (особенно если вам просто нужны первые несколько элементов итерабельного, например) и их следует избегать

в Python

но начиная с Python 2.6 и 3.0 вы можете использовать новую инфраструктуру ABC (абстрактный базовый класс) вместе с некоторыми встроенными ABCs, которые доступны в модуле collections:

from collections import Iterable

class MyObject(object):
    pass

mo = MyObject()
print isinstance(mo, Iterable)
Iterable.register(MyObject)
print isinstance(mo, Iterable)

print isinstance("abc", Iterable)

Теперь, является ли это желательным или на самом деле работает, это просто вопрос конвенции. Как видите, вы ... --8-->can зарегистрировать не итерируемый объект, а метод - и это будет вызывать исключение во время выполнения. Следовательно, isinstance приобретает "новое" значение - он просто проверяет" объявленную " совместимость типов, что является хорошим способом пойти в Python.

С другой стороны, если ваш объект не удовлетворяет интерфейс, который вам нужен, что вы собираетесь делать? Возьмем следующий пример:

from collections import Iterable
from traceback import print_exc

def check_and_raise(x):
    if not isinstance(x, Iterable):
        raise TypeError, "%s is not iterable" % x
    else:
        for i in x:
            print i

def just_iter(x):
    for i in x:
        print i


class NotIterable(object):
    pass

if __name__ == "__main__":
    try:
        check_and_raise(5)
    except:
        print_exc()
        print

    try:
        just_iter(5)
    except:
        print_exc()
        print

    try:
        Iterable.register(NotIterable)
        ni = NotIterable()
        check_and_raise(ni)
    except:
        print_exc()
        print

Если объект не удовлетворяет тому, что вы ожидаете, вы просто бросаете TypeError, но если была зарегистрирована правильная ABC, ваш чек непригоден. Наоборот, если __iter__ доступен метод Python автоматически распознает объект этого класса как итеративный.

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


Я нашел хорошее решение здесь:

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)

вы можете попробовать это:

def iterable(a):
    try:
        (x for x in a)
        return True
    except TypeError:
        return False

Если мы можем сделать генератор, который перебирает (но никогда не используйте генератор, поэтому она не займет много места), это повторяемое. Похоже на "да". Почему вам нужно определить, является ли переменная итерабельной в первую очередь?


по словам Python 2 Глоссарий, iterables являются

все типы последовательности (например,list, str и tuple) и некоторые типы не последовательности, такие как dict и file и объекты любых классов, которые вы определяете с помощью __iter__() или __getitem__() метод. Итерабли могут использоваться в цикле for и во многих других местах, где необходима последовательность (zip (), map (),...). Когда итерируемый объект передается в качестве аргумента встроенной функции ИТЭР(), это возвращает итератор для объекта.

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

try:
    for i in object_in_question:
        do_something
except TypeError:
    do_something_for_non_iterable

но если вам нужно проверить его явно, вы можете проверить итерацию по hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Вам нужно проверить оба, потому что strs не имеют __iter__ метод (по крайней мере, не в Python 2, в Python 3 они делают) и потому что generator объекты нет __getitem__ метод.


Я часто нахожу удобным, внутри моих скриптов, определить . (Теперь включает предложенное Альфой упрощение):

import collections

def iterable(obj):
    return isinstance(obj, collections.Iterable):

так что вы можете проверить, если любой объект является итерируемым в очень читабельном виде

if iterable(obj):
    # act on iterable
else:
    # not iterable

как вы сделали бы сcallable функции

EDIT: если у вас установлен numpy, вы можете просто сделать: от numpy import iterable, это просто что-то вроде

def iterable(obj):
    try: iter(obj)
    except: return False
    return True

если у вас нет numpy, вы можете просто реализовать этот код, или тот, что выше.


панды имеет встроенную функцию, как это:

from pandas.util.testing import isiterable

С Python 3.5 можно использовать введя модуль из стандартной библиотеки для типа операции:

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)

def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

это будет поддакивать всякой итерируемые объекты, но это будет скажите " нет " строкам в Python 2. (Вот что я хочу, например, когда рекурсивная функция может принимать строку или контейнер строк. В такой ситуации ... --17-->прошу прощения может привести к обфускодированию, и лучше сначала спросить разрешение.)

import numpy

class Yes:
    def __iter__(self):
        yield 1;
        yield 2;
        yield 3;

class No:
    pass

class Nope:
    def __iter__(self):
        return 'nonsense'

assert is_iterable(Yes())
assert is_iterable(range(3))
assert is_iterable((1,2,3))   # tuple
assert is_iterable([1,2,3])   # list
assert is_iterable({1,2,3})   # set
assert is_iterable({1:'one', 2:'two', 3:'three'})   # dictionary
assert is_iterable(numpy.array([1,2,3]))
assert is_iterable(bytearray("not really a string", 'utf-8'))

assert not is_iterable(No())
assert not is_iterable(Nope())
assert not is_iterable("string")
assert not is_iterable(42)
assert not is_iterable(True)
assert not is_iterable(None)

многие другие стратегии здесь скажут " да " строкам. Используйте их, если вы хотеть.

import collections
import numpy

assert isinstance("string", collections.Iterable)
assert isinstance("string", collections.Sequence)
assert numpy.iterable("string")
assert iter("string")
assert hasattr("string", '__getitem__')

Примечание: is_iterable () скажет " да " строкам типа bytes и bytearray.

  • bytes объекты в Python 3 являются iterable True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) в Python 2 такого типа нет.
  • bytearray объекты в Python 2 и 3 являются iterable True == is_iterable(bytearray(b"abc"))

О. П. hasattr(x, '__iter__') подход скажет " да "строкам в Python 3 и" нет " в Python 2 (независимо от того,'' или b'' или u''). Спасибо @LuisMasuelli за заметив, что он также подведет вас на багги __iter__.


самый простой способ, уважая в Python утиной типизацией, чтобы поймать ошибку (Python прекрасно знает, что он ожидает от объекта, чтобы стать итератором):

class A(object):
    def __getitem__(self, item):
        return something

class B(object):
    def __iter__(self):
        # Return a compliant iterator. Just an example
        return iter([])

class C(object):
    def __iter__(self):
        # Return crap
        return 1

class D(object): pass

def iterable(obj):
    try:
        iter(obj)
        return True
    except:
        return False

assert iterable(A())
assert iterable(B())
assert iterable(C())
assert not iterable(D())

Примечания:

  1. это различия не имеют значения, является ли объект не повторяемое, или глючит __iter__ реализовано, если тип исключения один и тот же: в любом случае вы не сможете выполнить итерацию объекта.
  2. Я думаю, что я поймите вашу озабоченность: как это callable существует как проверка, могу ли я также полагаться на типирование утки, чтобы поднять AttributeError Если __call__ не определено для моего объекта, но это не относится к итеративной проверке?

    Я не знаю ответа, но вы можете либо реализовать функцию, которую я (и другие пользователи) дал, либо просто поймать исключение в своем коде (ваша реализация в этой части будет похожа на функцию, которую я написал - Просто убедитесь, что вы изолируете создание итератора от остальной части код, чтобы вы могли захватить исключение и отличить его от другого TypeError.


на isiterable func при следующем коде возвращает True если объект является итерируемым. если это не iterable возвращает False

def isiterable(object_):
    return hasattr(type(object_), "__iter__")

пример

fruits = ("apple", "banana", "peach")
isiterable(fruits) # returns True

num = 345
isiterable(num) # returns False

isiterable(str) # returns False because str type is type class and it's not iterable.

hello = "hello dude !"
isiterable(hello) # returns True because as you know string objects are iterable

вместо проверки __iter__ атрибут, вы можете проверить для __len__ атрибут, который реализуется каждым python builtin iterable, включая строки.

>>> hasattr(1, "__len__")
False
>>> hasattr(1.3, "__len__")
False
>>> hasattr("a", "__len__")
True
>>> hasattr([1,2,3], "__len__")
True
>>> hasattr({1,2}, "__len__")
True
>>> hasattr({"a":1}, "__len__")
True
>>> hasattr(("a", 1), "__len__")
True

None-iterable объекты не будут реализовывать это по очевидным причинам. Однако он не улавливает определяемые пользователем итерабли, которые не реализуют его, а также выражения генератора, которые iter можно иметь дело. Однако это можно сделать в строке и добавив простой or проверка выражений генераторы устранят эту проблему. (Обратите внимание, что писать!--6--> бросить NameError. См.этой ответ.)

вы можете использовать GeneratorType из типов:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True

- - - - принятый ответ utdemir

(это делает его полезным для проверки, если это можно так назвать len на объект.)


это всегда ускользало от меня, почему python имеет callable(obj) -> bool а не iterable(obj) -> bool...
конечно, это легче сделать hasattr(obj,'__call__') даже если это медленнее.

так как почти каждый другой ответ рекомендует использовать try/except TypeError, где тестирование исключений обычно считается плохой практикой среди любого языка, вот реализация iterable(obj) -> bool Я стал больше любить и использовать часто:

ради python 2 я буду использовать лямбду только для этой дополнительной производительности повышение...
(в python 3 не имеет значения, что вы используете для определения функции, def имеет примерно такую же скорость как lambda)

iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')

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

большинство итерируемых объектов должны полагаться на __iter__ где специальные объекты падают обратно в __getitem__, хотя для итерации объекта требуется либо то, либо другое.
(и поскольку это стандарт, это влияет на C объекты)


помимо обычной попытки, вы можете запустить help.

temp= [1,2,3,4]
help(temp)

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

Примечание:это было бы то, что вы сделали бы вручную.