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

1086

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

hasattr(myObj, '__iter__')

Но я не уверен, насколько это глупо.

Виллем
источник
18
__getitem__также достаточно, чтобы сделать объект повторяемым
Кос
4
FWIW: iter(myObj)успешно, если isinstance(myObj, dict), поэтому, если вы смотрите на a, myObjкоторая может быть последовательностью dicts или одиночной dict, вы добьетесь успеха в обоих случаях. Тонкость, которая важна, если вы хотите знать, что такое последовательность, а что нет. (в Python 2)
Бен Мошер
7
__getitem__также достаточно, чтобы сделать объект итеративным ... если он начинается с нулевого индекса .
Карлос А. Гомес

Ответы:

28

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

from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower

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

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

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

Мы также использовали iter()в нашем коде для этой цели, но в последнее время я все больше и больше раздражаюсь объектами, которые __getitem__считаются повторяемыми. Существуют веские причины иметь __getitem__не повторяемый объект, и с ними приведенный выше код не работает должным образом. В качестве примера из реальной жизни мы можем использовать Faker . Приведенный выше код сообщает, что он повторяется, но на самом деле попытка его повторить вызывает AttributeError(протестировано с Faker 4.0.2):

>>> from faker import Faker
>>> fake = Faker()
>>> iter(fake)    # No exception, must be iterable
<iterator object at 0x7f1c71db58d0>
>>> list(fake)    # Ooops
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/.../site-packages/faker/proxy.py", line 59, in __getitem__
    return self._factory_map[locale.replace('-', '_')]
AttributeError: 'int' object has no attribute 'replace'

Если бы мы использовали insinstance(), мы не могли бы случайно считать экземпляры Faker (или любые другие объекты, имеющие только __getitem__) итеративными:

>>> from collections.abc import Iterable
>>> from faker import Faker
>>> isinstance(Faker(), Iterable)
False

В предыдущих ответах отмечалось, что использование iter()безопаснее, поскольку старый способ реализации итерации в Python был основан __getitem__и этот isinstance()подход не обнаружит этого. Это могло бы быть правдой со старыми версиями Python, но, основываясь на моем довольно исчерпывающем тестировании, в isinstance()настоящее время отлично работает. Единственный случай, когда isinstance()это не сработало, но сработало iter(), было UserDictпри использовании Python 2. Если это уместно, можно использовать это isinstance(item, (Iterable, UserDict))для покрытия.

Пекка Кларк
источник
1
Также typing.Dictсчитается повторяемым, iter(Dict)но list(Dict)завершается с ошибкой TypeError: Parameters to generic types must be types. Got 0.. Как и ожидалось, isinstance(Dict, Iterable)возвращается false.
Пекка Кларк
1
Я пришел к такому же выводу, но по разным причинам. Использование iterвызвало некоторые из нашего кода, который использовал «предварительное кэширование», чтобы замедлить без необходимости. Если __iter__код медленный, вызов будет iter... в любое время, когда вы просто захотите посмотреть, является ли что-то итеративным.
Торволен
843
  1. Проверка __iter__работает на типах последовательностей, но не работает, например, в строках в Python 2 . Я также хотел бы знать правильный ответ, до тех пор, здесь есть одна возможность (которая также будет работать со строками):

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

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

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

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

    ...

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

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

    Однако, это не проверяет классы, через которые можно пройти итерацию __getitem__.

Мик
источник
34
[e for e in my_object]может вызвать исключение по другим причинам, т.е. my_objectне определено или возможные ошибки в my_objectреализации.
Ник Дандулакис
37
Строка представляет собой последовательность ( isinstance('', Sequence) == True) , и , как любая последовательность она является итерацией ( isinstance('', Iterable)). Хотя hasattr('', '__iter__') == Falseи это может сбить с толку.
JFS
82
Если my_objectон очень большой (скажем, бесконечный, как itertools.count()), ваше понимание списка займет много времени / памяти. Лучше сделать генератор, который никогда не будет пытаться создать (потенциально бесконечный) список.
Крис Латс
14
Что если some_object выбрасывает TypeError, вызванный другой причиной (ошибками и т. Д.)? Как мы можем сказать это из "Не повторяемой ошибки типа"?
Шаунг
54
Обратите внимание, что в Python 3: hasattr(u"hello", '__iter__')возвращаетсяTrue
Карлос
573

Утка печатать

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__() методом. Единственный надежный способ определить, является ли объект итеративным, - это вызвать iter(obj).

Георг Шолли
источник
18
Из «Свободного Python» Лучано Рамальо: Начиная с Python 3.4, наиболее точный способ проверить, является ли объект x итеративным, - это вызвать iter (x) и обработать исключение TypeError, если это не так. Это более точно, чем использование isinstance (x, abc.Iterable), потому что iter (x) также учитывает устаревший метод getitem , а Iterable ABC - нет.
RDB
В случае, если вы думаете «о, я просто isinstance(x, (collections.Iterable, collections.Sequence))вместо iter(x)», обратите внимание, что это все равно не обнаружит итеративный объект, который реализует только, __getitem__но не __len__. Используйте iter(x)и поймайте исключение.
Дейл
Ваш второй ответ не работает. В PyUNO, если я делаю iter(slide1), все идет хорошо, однако isinstance(slide1, Iterable)броски TypeError: issubclass() arg 1 must be a class.
Привет, Ангел,
@ Привет-Ангел звучит как ошибка в PyUNOОбратите внимание, что issubclass()вместо сообщения указано ваше сообщение об ошибке isinstance().
Георг Шолли
2
Вызов iter () для объекта может быть дорогостоящей операцией (см. DataLoader в Pytorch, который запускает / порождает несколько процессов в iter ()).
szali
126

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

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

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

факты

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

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

    б) oимеет __getitem__метод.

  2. Проверка на наличие экземпляра Iterableили Sequence, или проверка на атрибут __iter__недостаточно.

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

  4. В самом общем смысле, нет никакого способа проверить, является ли итератор, возвращаемый пользователем iter, нормальным, кроме как попробовать его.

  5. Если объект oреализуется __iter__, iterфункция гарантирует, что возвращаемый объект __iter__является итератором. Нет проверки работоспособности, если объект только реализует __getitem__.

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

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

for петли

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

Когда вы используете for item in oкакой-либо повторяемый объект o, Python вызывает iter(o)и ожидает объект итератора в качестве возвращаемого значения. Итератор - это любой объект, который реализует __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

Итерация по 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

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

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

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

Итерация по экземпляру BasicIterableработает как ожидалось: Python создает итератор, который пытается извлечь элементы по индексу, начиная с нуля, пока не IndexErrorбудет поднято значение. Метод демонстрационного объекта __getitem__просто возвращает значение, itemкоторое было передано в качестве аргумента __getitem__(self, item)итератором, возвращаемым 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

Вот еще один пример, чтобы показать, как возвращаемый итератор 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__к list.__getitem__( с помощью квадратных скобок обозначения), будет работать нормально:

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

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

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

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

timgeb
источник
так что безопасно определить предикат is_iterable, возвращаясь Trueв tryблоке и Falseв except TypeErrorблоке?
alancalvitti
Это отличный ответ. Я думаю, что это подчеркивает неинтуитивный и неудачный характер протокола getitem. Это никогда не должно было быть добавлено.
Нил Дж
31

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

В Python хорошей практикой является «попробовать и посмотреть» вместо «проверки».

jldupont
источник
9
"утка печатать" я верю? :)
Виллем
9
@willem: или «не проси разрешения, но прощения» ;-)
jldupont
14
@willem Стили «разрешение» и «прощение» квалифицируются как типирование утки. Если вы спросите, что может сделать объект , а не то, что он есть , это будет утка. Если вы используете интроспекцию, это «разрешение»; если вы просто попытаетесь сделать это и посмотреть, работает ли это или нет, это «прощение».
Марк Рид
22

В Python <= 2.5 вы не можете и не должны - итерируемый был «неформальным» интерфейсом.

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

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)

Теперь, является ли это желательным или действительно работает, это просто вопрос соглашений. Как видите, вы можете зарегистрировать не повторяемый объект как Iterable - и он вызовет исключение во время выполнения. Следовательно, 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 автоматически распознает объект этого класса как Iterable.

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

Алан Францони
источник
13
не используйте голые except:в примере кода для начинающих. Это способствует плохой практике.
JFS
JFS: Я бы не стал, но мне нужно было пройти через несколько кодов, вызывающих исключения, и я не хотел поймать конкретное исключение ... Я думаю, цель этого кода довольно ясна.
Алан Францони
21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

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

badp
источник
3
Технически, во время итерации ваши вычисления могут привести к ошибкам TypeErrorи сбить вас с толку, но в основном да.
Крис Латс
6
@Willem: пожалуйста, используйте timeit для выполнения теста. Python исключения часто бывают быстрее, чем if-операторы. Они могут пройти немного более короткий путь через переводчика.
С.Лотт
2
@willem: IronPython имеет медленные (по сравнению с CPython) исключения.
JFS
2
Рабочая попытка: утверждение действительно быстро. Так что, если у вас есть несколько исключений, попробуйте-исключение быстро. Если вы ожидаете много исключений, «если» может быть быстрее.
Арне Бабенхаузерхайде
2
Не должен ли объект исключения быть пойман путем добавления " as e" после TypeErrorвместо " , e"?
HelloGoodbye
21

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

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)
Rotareti
источник
18

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

hasattr(obj, '__contains__')

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

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

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

Ноты:

  • философия Python «просить прощения, а не разрешения» не работает, когда, например, в списке есть как итерируемые, так и неитерируемые, и вам нужно обрабатывать каждый элемент по-разному в соответствии с его типом (обработка итеративных элементов при попытке и не Итериблс, за исключением того, что это сработало бы , но это выглядело бы неприглядно и вводило в заблуждение)
  • Решения этой проблемы, которые пытаются фактически выполнить итерацию по объекту (например, [x для x в obj]), чтобы проверить, является ли он итеративным, могут привести к значительным потерям производительности для больших итерируемых элементов (особенно, если вам просто нужны первые несколько элементов итерируемого, для пример) и его следует избегать
Влад
источник
3
Хорошо, но почему бы не использовать модуль коллекций, как это предлагается в stackoverflow.com/questions/1952464/… ? Кажется более выразительным для меня.
Дейв Абрахамс
1
Он короче (и не требует дополнительного импорта) без потери ясности: наличие метода «содержит» выглядит как естественный способ проверить, является ли что-то набором объектов.
Влад
46
То, что что-то может содержать что-то, не обязательно означает, что оно повторяется. Например, пользователь может проверить, находится ли точка в трехмерном кубе, но как бы вы перебрали этот объект?
Кейси Кубалл
13
Это неверно Сама итерация не поддерживает содержит , по крайней мере с Python 3.4.
Питер Шиннерс
15

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

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

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

Крис Лутц
источник
Как насчет iterable(itertools.repeat(0))? :)
badp
5
@badp, (x for x in a)просто создает генератор, он не выполняет итераций для a.
catchmeifyoutry
5
Попытка (x for x in a)точно эквивалентна попытке iterator = iter(a)? Или есть несколько случаев, когда они разные?
максимум
Разве не for _ in a: breakпроще? Это медленнее?
Mr_and_Mrs_D
2
@Mr_and_Mrs_D это плохо, если тестируемый объект является итератором, который повторяется впоследствии (это будет 1 элемент, так как его позиция не может быть сброшена), создание генераторов мусора не выполняет итерацию по объекту, поскольку они не повторяются, хотя я не уверен, что он вызовет TypeError на 100%, если не будет повторяться.
Tcll
13

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

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)
jbochi
источник
11

Согласно Глоссарию Python 2 , итерации

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

Конечно, учитывая общий стиль кодирования для 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__метода.

Anaphory
источник
8

В моих сценариях я часто нахожу удобным определение iterableфункции. (Теперь включает в себя предложенное Alfe упрощение):

import collections

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

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

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

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

РЕДАКТИРОВАТЬ: если у вас установлен NumPy, вы можете просто сделать: от numpy import iterable, который просто что-то вроде

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

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

fmonegaglia
источник
3
Всякий раз, когда вы делаете что-то подобное if x: return True else: return False( xбудучи логическим), вы можете написать это как return x. В вашем случае return isinstance(…)без каких-либо if.
Alfe
Поскольку вы признаете, что решение Alfe лучше, почему вы не отредактировали свой ответ, чтобы просто сказать это? Вместо этого у вас теперь есть ОБА версии в вашем ответе. Ненужное многословие. Отправка редактирования, чтобы исправить это.
ToolmakerSteve
2
Вы должны перехватить «TypeError» в строке «кроме: возвращать False». Поймать все это плохая картина.
Мариуш Джамро
Знай это. Я перевел этот кусок кода из библиотеки NumPy, которая использует общее исключение.
fmonegaglia
Тот факт, что код взят из NumPy, не означает, что он хороший ... шаблон или нет, единственный раз, когда нужно все поймать, это если вы явно обрабатываете ошибки внутри своей программы.
Tcll
5

имеет такую ​​встроенную функцию:

from pandas.util.testing import isiterable
Сёрен
источник
это однако только смотрит, есть ли __iter__и не действительно заботятся о последовательностях и подобных.
Свинец
4

Мне всегда было трудно понять, почему 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)

Tcll
источник
он не предоставляет рабочий код, не говоря уже о разговоре о производительности Python ... хотя этот ответ был на самом деле просто для удобства, как я видел здесь уже много раз.
Tcll
3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

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

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 () скажет yes строкам типа bytesи bytearray.

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

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

Боб Стейн
источник
2

Самый простой способ, с учетом утилитарной типизации 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существует проверка, если я мог бы также полагаться на типизацию утки, чтобы поднять AttributeErrorif __call__, не определенную для моего объекта, но это не относится к повторяющейся проверке?

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

Луис Масуэлли
источник
1

Функция isiterableв следующем коде возвращает, Trueесли объект является итеративным. если это не повторяется, возвращает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
кочевник
источник
2
так много подробных ответов выше со многими ответами, и вы добавляете необъяснимый ответ ... meh
Nrzonline
Пожалуйста, не публикуйте голый код. Также включите объяснение того, что это делает.
Джонатан Ми
1

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

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

Не повторяемые объекты не будут реализовывать это по очевидным причинам. Однако он не перехватывает определяемые пользователем итерации, которые его не реализуют, и выражения генератора, с которыми iterможно иметь дело. Однако это можно сделать в строке, и добавление простой orпроверки выражений для генераторов решит эту проблему. (Обратите внимание, что написание type(my_generator_expression) == generatorбудет бросать NameError. Вместо этого обратитесь к этому ответу.)

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

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

--- принял ответ утдемир

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

DarthCadeus
источник
к сожалению, не все итерируемые объекты используют __len__... для этого случая обычно неправильно вычислять расстояние между двумя объектами. где obj.dist()можно легко заменить.
Tcll
Да. Большинство пользовательских итераций реализуют iter и getitem, но не len. Тем не менее, встроенные типы делают, и если вы хотите проверить, можете ли вы вызывать функцию len на нем, проверка на len более безопасна. Но ты прав.
ДартКадеус
0

Не совсем «правильно», но может служить быстрой проверкой большинства распространенных типов, таких как строки, кортежи, числа с плавающей точкой и т. Д.

>>> '__iter__' in dir('sds')
True
>>> '__iter__' in dir(56)
False
>>> '__iter__' in dir([5,6,9,8])
True
>>> '__iter__' in dir({'jh':'ff'})
True
>>> '__iter__' in dir({'jh'})
True
>>> '__iter__' in dir(56.9865)
False
Ян Мусил
источник
0

Вроде поздно на вечеринку, но я задал себе этот вопрос и увидел это, а затем подумал об ответе. Я не знаю, если кто-то уже опубликовал это. Но по сути, я заметил, что все повторяемые типы имеют __getitem __ () . Вот как вы можете проверить, является ли объект итеративным, даже не пытаясь. (Пун предназначен)

def is_attr(arg):
    return '__getitem__' in dir(arg)
lakam99
источник
К сожалению, это ненадежно. Пример
тимгеб
1
Объекты Set - еще один контрпример.
Рэймонд Хеттингер