Як в Python я можу визначити, чи є об'єкт ітерабельним?


1083

Чи існує такий метод, як isiterable? Єдине рішення, яке я знайшов поки що - це дзвонити

hasattr(myObj, '__iter__')

Але я не впевнений, наскільки це нерозумно.


18
__getitem__також достатньо, щоб зробити об’єкт ітерабельним
Кос

4
FWIW: iter(myObj)досягає успіху, якщо isinstance(myObj, dict), якщо ви дивитесь на то, myObjщо може бути послідовністю dicts або одиничним dict, ви досягнете успіху в обох випадках. Тонкість, яка важлива, якщо ви хочете знати, що таке послідовність, а що ні. (у Python 2)
Бен Мошер

7
__getitem__також достатньо, щоб зробити об’єкт ітерабельним ... якщо він починається з нульового індексу .
Карлос А. Гомес

Відповіді:


26

Я вивчав цю проблему зовсім недавно. Виходячи з цього, я робив висновок, що на сьогодні це найкращий підхід:

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)повернення помилкове.
Pekka Klärck

1
Я прийшов до того ж висновку, але з різних причин. Використання iterспричинило деякий наш код, який використовував "попереднє кешування", щоб зайве гальмувати. Якщо __iter__код повільний, то і дзвонить iter... будь-коли, коли ви просто захочете дізнатися, чи щось є ітерабельним.
затоплений

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

    Пітонічний стиль програмування, який визначає тип об'єкта шляхом перевірки його методу чи підпису атрибутів, а не за явним відношенням до об'єкта якогось типу ("Якщо він схожий на качку і трясеться як качка , він повинен бути качкою ".) Підкреслюючи інтерфейси замість конкретних типів, добре розроблений код покращує його гнучкість, дозволяючи поліморфну ​​заміну. Введення качок уникає тестів, використовуючи тип () або isin substance (). Замість цього, як правило, використовується стиль програмування 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, спричинений також з інших причин (помилки тощо)? Як ми можемо сказати це з "Не ітерабельного TypeError"?
Шаун

54
Зауважте, що в Python 3: hasattr(u"hello", '__iter__')віддачаTrue
Карлос

572

Введення качки

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
З "Fluent Python" Luciano Ramalho: Станом на Python 3.4, найточніший спосіб перевірити, чи є об'єкт x ітерабельним, - викликати iter (x) та обробляти виключення TypeError, якщо його немає. Це точніше, ніж використання isin substance (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

125

Я хотів би, щоб пролити трохи більше світла на взаємодію 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__існує з міркувань відсталої сумісності і може піти в майбутньому (хоча це ще не застаріло, як я це пишу).


тож безпечно визначити присудок is_iterable, повертаючись Trueу tryблоці та Falseв except TypeErrorблоці?
alancalvitti

Це чудова відповідь. Я думаю, що це підкреслює неінтуїтивний та нещасний характер протоколу getitem. Його ніколи не слід було додавати.
Ніл Г

31

Цього недостатньо: об'єкт, що повертається, __iter__повинен реалізувати протокол ітерації (тобто nextметод). Дивіться відповідний розділ у документації .

У Python гарною практикою є "спробувати і побачити" замість "перевірки".


9
"качка набирає" я вірю? :)
willem

9
@willem: або "не проси дозволу, а пробачення" ;-)
jldupont

14
@willem Обидва стилі "дозволу" та "прощення" кваліфікуються як набрання качки. Якщо ви запитаєте, що може робити об’єкт , а не що це таке , це качка набирає текст. Якщо ви використовуєте самоаналіз, це "дозвіл"; якщо ви просто спробуєте це зробити і подивіться, працює він чи ні, це "прощення".
Марк Рід

22

У Python <= 2.5 ви не можете і не повинні - iterable був "неофіційним" інтерфейсом.

Але оскільки 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 - і це призведе до виключення під час виконання. Отже, істота набуває "нового" значення - вона просто перевіряє "заявлену" сумісність типу, що є хорошим способом пройти в 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

Не запускайте перевірки, щоб побачити, чи справді ваша качка - це качка, щоб побачити, чи є вона ітерабельна чи ні, поводьтеся з нею так, ніби вона є, і скаржиться, якщо її немає.


3
Технічно під час ітерації ваші обчислення можуть кинути TypeErrorі кинути вас сюди, але в основному так.
Кріс Лутц

6
@willem: Будь ласка, використовуйте timeit, щоб виконати показник. Винятки Python часто швидше, ніж if-заяви. Вони можуть пройти трохи коротший шлях через перекладача.
S.Lott

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)

18

Найкраще рішення, яке я знайшов поки що:

hasattr(obj, '__contains__')

яка в основному перевіряє, чи об'єкт реалізує inоператор.

Переваги (жодне з інших рішень не має всіх трьох):

  • це вираз (працює як лямбда , на відміну від спроби ... крім варіанту)
  • він (повинен бути) реалізований усіма ітерабелями, включаючи рядки (на відміну від __iter__)
  • працює на будь-якому Python> = 2.5

Примітки:

  • філософія Python "просити пробачення, а не дозволу" не спрацьовує, коли, наприклад, у списку у вас є ітерабелі, і не-ітерабелі, і вам потрібно по-різному ставитися до кожного елемента відповідно до його типу (обробляти ітерабелі спробувати iterables за винятком буде працювати, але це буде виглядати непогано і оманливо)
  • рішення цієї проблеми, які намагаються насправді повторити ітерацію над об’єктом (наприклад, [x for x in obj]), щоб перевірити, чи є воно ітерабельним, може спричинити значні штрафи за ефективність великих ітерабелів (особливо, якщо вам просто потрібні перші кілька елементів ітерабельної програми, для приклад) і цього слід уникати

3
Приємно, але чому б не використовувати модуль колекцій, як запропоновано в stackoverflow.com/questions/1952464/… ? Здається мені виразніше.
Дейв Абрахамс

1
Це коротше (і не потребує додаткового імпорту), не втрачаючи ясності: використання методу "містить" вважається природним способом перевірити, чи щось є колекцією об'єктів.
Влад

46
Тільки тому, що щось може містити, це не обов'язково означає, що це можливо. Наприклад, користувач може перевірити, чи є точка у 3D-кубі, але як би ви повторили цей об’єкт?
Кейсі Кубалл

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 елемент, оскільки його положення неможливо скинути), створюючи генератори сміття, не ітераціюйте над об'єктом, оскільки вони не повторюються, хоча я не впевнений, що це на 100% підвищить TypeError, якщо не перетворити.
Tcll

13

Я знайшов гарне рішення тут :

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

10

Згідно з Глосарієм 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__методу.


8

Я часто знаходжу зручне всередині своїх сценаріїв визначення iterableфункції. (Зараз включено запропоноване Альфе спрощення):

import collections

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

тож ви можете перевірити, чи будь-який об’єкт є ітерабельним у дуже читабельній формі

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

як ви зробили б ізcallable функцією

EDIT: якщо у вас встановлено numpy, ви можете просто зробити: from numpy import iterable, що просто щось на кшталт

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

Якщо у вас немає numpy, ви можете просто реалізувати цей код або той, що описаний вище.


3
Щоразу, коли ви робите щось на зразок if x: return True else: return Falsexбулевим), ви можете написати це як return x. У вашому випадку return isinstance(…)без жодного if.
Алфе

Оскільки ви визнаєте, що рішення Альфе краще, чому ви не редагували свою відповідь, щоб просто сказати це? Натомість тепер у вашій відповіді є ОСІННІ версії. Непотрібна багатослівність. Надіславши правку, щоб виправити це.
ToolmakerSteve

2
Ви повинні зловити "TypeError" у рядку `за винятком: return False`. Ловити все - це погана закономірність.
Маріуш Джамро

Знати, що. Я переклав цей фрагмент коду з бібліотеки NumPy, який використовує загальний виняток.
fmonegaglia

Тільки тому, що код взято з NumPy, не означає, що він хороший ... візерунок чи ні, єдиний час, коли потрібно зробити все, слід зробити, якщо ви явно обробляєте помилки в роботі програми.
Tcll

5

має вбудовану функцію так:

from pandas.util.testing import isiterable

однак це лише виглядає, чи є, __iter__а чи не насправді піклується про послідовності та подібне.
Свинець

4

Мене завжди уникають, чому пітон є, 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

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 () скаже «так» рядкам типу 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існує перевірка, якщо я можу також розраховувати на тип качки, щоб підняти, AttributeErrorякщо __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
стільки детальних відповідей вище з багатьма відгуками, і ви кидаєте незрозумілу відповідь ... Мех
Nrzonline

Будь ласка, не публікуйте голий код. Також додайте пояснення, що це робить.
Джонатан Мі

1

Замість перевірки на __iter__атрибут, ви можете перевірити __len__атрибут, який реалізується кожним збудованим пітоном в ньому, включаючи рядки.

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

--- прийнята відповідь utdemir

(Це робить корисним для перевірки, чи можна зателефонувати lenпо об'єкту.)


на жаль, не всі ітерабельні об'єкти використовують __len__... у цьому випадку зазвичай неправильне використання обчислення відстані між двома об'єктами. де obj.dist()можна було легко замінити.
Tcll

Так. Більшість визначених користувачем iterables реалізують iter та getitem, але не len. Однак вбудовані типи роблять, і якщо ви хочете перевірити, чи можете ви зателефонувати на функцію len, перевірка на наявність len є більш безпечною. Але ви праві.
DarthCadeus

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)

На жаль, це ненадійно. Приклад
timgeb

1
Встановлені об'єкти - ще один контрприклад.
Реймонд Хеттінгер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.