Есть ли способ, например isiterable
? Единственным решением, которое я нашел до сих пор, является вызов
hasattr(myObj, '__iter__')
Но я не уверен, насколько это безупречно.
Есть ли способ, например isiterable
? Единственным решением, которое я нашел до сих пор, является вызов
hasattr(myObj, '__iter__')
Но я не уверен, насколько это безупречно.
Проверка на __iter__
работает с типами последовательностей, но не работает, например, в строках Python 2. Я также хотел бы знать правильный ответ, до тех пор, здесь есть одна возможность (которая также будет работать со строками):
try:
some_object_iterator = iter(some_object)
except TypeError as te:
print some_object, 'is not iterable'
Встроенная iter
проверяет метод __iter__
или, в случае строк, метод __getitem__
.
Другой общий питонический подход - предполагать итеративность, а затем изящно проваливаться, если она не работает с данным объектом. Глоссарий Python:
Стиль программирования Pythonic, который определяет тип объекта путем проверки его метода или сигнатуры атрибута, а не путем явного отношения к какому-либо объекту типа ("Если он выглядит как утка и крякает как утка, он должен быть уткой".) Подчеркивая интерфейсы В отличие от конкретных типов, хорошо разработанный код повышает его гибкость, позволяя полиморфную замену. Утиная типография избегает тестов с использованием type() или isinstance(). Вместо этого он обычно использует стиль программирования EAFP (Проще просить прощения, чем разрешения).
...
try: _ = (e for e in my_object) except TypeError: print my_object, 'is not iterable'
Модуль collections
предоставляет некоторые абстрактные базовые классы, которые позволяют запрашивать классы или экземпляры, предоставляют ли они определенную функциональность, например:
from collections.abc import Iterable
if isinstance(e, 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__()
итерацию с помощью__getitem__()
. Единственный надежный способ определить, является ли объект итеративным, - это вызватьiter(obj)
.
Я хотел бы пролить немного больше света на взаимодействие iter
, __iter__
и __getitem__
и то, что происходит за кулисами. Вооружившись этими знаниями, вы сможете понять, почему лучшее, что вы можете сделать, это
try:
iter(maybe_iterable)
print('iteration will probably work')
except TypeError:
print('not iterable')
Сначала я перечислю факты, а затем быстро напомню, что происходит, когда вы используете цикл for
в python, а затем обсудите их, чтобы проиллюстрировать факты.
Вы можете получить итератор из любого объекта o
, вызвав iter(o)
, если выполняется хотя бы одно из следующих условий:
a) o
имеет метод __iter__
, который возвращает объект итератора. Итератор - это любой объект с методом __iter__
и __next__
(Python 2: next
).
b) o
имеет метод __getitem__
.
Проверка экземпляра Iterable
или Sequence
или проверка наличия
атрибута __iter__
недостаточно.
Если объект o
реализует только __getitem__
, но не __iter__
, iter(o)
создаст
итератор, который пытается извлечь элементы из o
по целочисленному индексу, начиная с индекса 0. итератор будет перехватывать любой IndexError
(но не другие ошибки), который возникает, а затем сам вызывает StopIteration
.
В самом общем смысле, нет никакого способа проверить, является ли итератор, возвращенный iter
нормальным, кроме как попробовать его.
Если объект o
реализует __iter__
, функция iter
обеспечит
что объект, возвращаемый __iter__
, является итератором. Там нет проверки вменяемости
если объект только реализует __getitem__
.
__iter__
побеждает. Если объект o
реализует как __iter__
, так и __getitem__
, iter(o)
вызовет __iter__
.
Если вы хотите сделать свои собственные объекты итеративными, всегда наследуйте от абстрактного базового класса Iterable
или одного из его подклассов. Вам придется либо реализовать метод __iter__
, либо он будет предоставлен, как в случае Sequence
.
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__
, аIterable
ABC - нет.
В пункте 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: 'Qaru', 2: '!',
... 4: 'not printed',
... 'x': 'not printed'})
>>> for x in w:
... print(x)
...
hi
Qaru
!
В пунктах 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__
, заключаются в следующем:
__iter__
, экземпляры будут считаться итеративными, а isinstance(o, collections.abc.Iterable)
вернет True
.__iter__
, не является итератором, iter
немедленно завершится ошибкой и вызовет TypeError
.__getitem__
существует по причинам обратной совместимости. Цитирую снова из Fluent Python:Вот почему любая последовательность Python является итеративной: все они реализуют
__getitem__
. По факту, стандартные последовательности также реализуют__iter__
, и ваш тоже должен, потому что специальная обработка__getitem__
существует по причинам обратной совместимости и может быть ушел в будущее (хотя это не устарело, как я пишу это).
Этого недостаточно: объект, возвращаемый __iter__
, должен реализовать протокол итерации (т.е. метод next
). См. Соответствующий раздел в документации.
В Python хорошей практикой является "попытаться увидеть" вместо "проверки".
В 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 - и он вызовет исключение во время выполнения. Следовательно, 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 очень полезна.
try:
#treat object as iterable
except TypeError, e:
#object is not actually iterable
Не выполняйте проверки, чтобы увидеть , если ваша утка действительно утка, чтобы увидеть, является ли она итерируемой или нет, рассматривайте ее так, как если бы она была, и жалуются, если это не так.
Лучшее решение, которое я нашел до сих пор:
hasattr(obj, '__contains__')
который в основном проверяет, реализует ли объект оператор in
.
Преимущества (ни одно из других решений не имеет всех трех):
__iter__
)Примечания:
Вы можете попробовать следующее:
def iterable(a):
try:
(x for x in a)
return True
except TypeError:
return False
Если мы можем сделать генератор, который выполняет итерацию по нему (но никогда не используйте генератор, чтобы он не занимал место), он повторяется. Кажется, что-то вроде "духа". Почему вам нужно определить, является ли переменная итерируемой в первую очередь?
Начиная с Python 3.5 вы можете использовать модуль ввода из стандартной библиотеки для вещей, связанных с типами:
from typing import Iterable
...
if isinstance(my_item, Iterable):
print(True)
Я нашел хорошее решение здесь:
isiterable = lambda obj: isinstance(obj, basestring) \
or getattr(obj, '__iter__', False)
Согласно Глоссарию 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__")
. Вы должны проверить оба, потому что str
не имеет метода __iter__
(по крайней мере, не в Python 2, в Python 3 они есть) и потому что у объектов generator
нет метода __getitem__
.
Я часто нахожу удобным внутри своих скриптов определение функции iterable
.
(Теперь включает в себя Alfe предложил упрощение):
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, вы можете просто реализовать этот код или выше.
pandas имеет встроенный так:
from pandas.util.testing import isiterable
def is_iterable(x):
try:
0 in x
except TypeError:
return False
else:
return True
Это скажет "да" всем типам итерируемых объектов, но не укажет на строки в Python 2. (Это то, что я хочу, например, когда рекурсивная функция может принимать строку или контейнер строк. В этой ситуации просить прощения может привести к обфуску, и лучше сначала спросите разрешения.)
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"))
Подход O.P. hasattr(x, '__iter__')
будет указывать да на строки в Python 3 и нет в Python 2 (независимо от того, ''
или b''
или u''
). Спасибо @LuisMasuelli за то, что он заметил, это также приведет вас к ошибкам __iter__
.
Это всегда ускользало от меня относительно того, почему в 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)
Самый простой способ, уважающий Python duck typing, заключается в том, чтобы поймать ошибку (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())
Примечания
__iter__
, если тип исключения тот же: в любом случае вы не сможете выполнить итерацию объекта.Думаю, я понимаю вашу обеспокоенность: как callable
существует как проверка, могу ли я также полагаться на утиную печать, чтобы поднять AttributeError
, если __call__
не определен для моего объекта, но это не случай для повторной проверки?
Я не знаю ответа, но вы можете либо реализовать функцию я (и других пользователей), либо просто уловить исключение в вашем коде (ваша реализация в этой части будет похожа на функцию, которую я написал, - просто убедитесь, что вы изолируете создание итератора от остальной части кода, чтобы вы могли зафиксировать исключение и отличить его от другого TypeError
.
Функция isiterable
в следующем коде возвращает True
, если объект iserable. если он не повторяется, возвращает 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 not iterable.
hello = "hello dude !"
isiterable(hello) # returns True because as you know string objects are iterable
Вместо проверки атрибута __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
. NameError
обратитесь к этому ответу.)
Вы можете использовать GeneratorType из типов:
>>> import types >>> types.GeneratorType <class 'generator'> >>> gen = (i for i in range(10)) >>> isinstance(gen, types.GeneratorType) True
--- принял ответ утдемир
(Это делает его полезным для проверки, можете ли вы вызвать len
на объекте.)
Как-то поздно на вечеринку, но я задал себе этот вопрос и увидел это, а затем подумал об ответе. Я не знаю, если кто-то уже опубликовал это. Но, по сути, я заметил, что у всех итерируемых типов есть "getitem". Вот как вы можете проверить, является ли объект итеративным, даже не пытаясь. (Пун задумано)
def is_attr(arg):
return '__getitem__' in dir(arg)
Не совсем "правильно", но может служить быстрой проверкой большинства распространенных типов, таких как строки, кортежи, числа с плавающей запятой и т.д.
>>> '__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
это работает с диктовками, содержащими итерации (список,...), которые могут содержать диктовки. Python 3 для Python 2 unicode
также должны быть исключены из итерации. Также могут быть некоторые итерации, которые не работают, о которых я не знаю. (т.е. приведет к бесконечной рекурсии)
from collections.abc import Iterable
def deep_omit(d, keys):
if isinstance(d, dict):
for k in keys:
d.pop(k, None)
for v in d.values():
deep_omit(v, keys)
elif isinstance(d, Iterable) and not isinstance(d, str):
for e in d:
deep_omit(e, keys)
return d
Помимо регулярных попыток и кроме, вы можете запустить помощь.
temp= [1,2,3,4]
help(temp)
help предоставит все методы, которые могут быть запущены на этом объекте (это может быть любой объект, а может и не быть списком в соответствии с примером), что в данном случае является temp.
Примечание: это будет то, что вы будете делать вручную.