14

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

14

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

hasattr(myObj, '__iter__')

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

5 ответ(ов)

0

В Python версии ⇐ 2.5 вы не можете и не должны использовать проверку на итерируемость, так как итерируемые объекты рассматривались как "неформальный" интерфейс.

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

from collections import Iterable

class MyObject(object):
    pass

mo = MyObject()
print(isinstance(mo, Iterable))  # False
Iterable.register(MyObject)
print(isinstance(mo, Iterable))  # True

print(isinstance("abc", Iterable))  # True

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

0

В приведенном коде используется конструкция try...except, чтобы обрабатывать ситуацию, когда объект не является итерируемым. Вот как это можно перевести на русский в стиле ответа на StackOverflow:

try:
  # обращаться к объекту как к итерируемому
except TypeError as e:
  # объект не является итерируемым

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

0

Вы можете попробовать следующее:

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

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

0

Лучшее решение, которое я нашел на данный момент:

hasattr(obj, '__contains__')

Это проверяет, реализует ли объект оператор in.

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

  • Это выражение (работает как lambda, в отличие от варианта с try...except)
  • Это должно быть реализовано всеми итерируемыми объектами, включая строки (в отличие от __iter__)
  • Работает на любой версии Python >= 2.5

Примечания:

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

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

import collections

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

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

if iterable(obj):
    # действия с итерируемым объектом
else:
    # не итерируемый объект

Это похоже на использование функции callable.

Редактирование: Если у вас установлен NumPy, вы можете просто использовать: from numpy import iterable, которая по сути является чем-то таким:

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

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь