有没有像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 风格是假定一个可迭代对象,如果在给定对象上无法正常工作,则优雅地处理失败。Python 词汇表:
Pythonic 编程风格通过检查方法或属性签名来确定对象的类型,而不是通过与某些类型对象的显式关系来确定其类型 ("If it looks like a duck and quacks like a duck, it must be a duck.")。通过强调接口而不是特定类型,设计良好的代码通过允许多态替换来提高其灵活性。Duck-typing 避免使用 type() 或 isinstance() 进行测试。相反,它通常采用 EAFP(异常即是 Pythonic)编程风格。
...
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__
进行迭代的类。
[e for e in my_object]
可能会因为其他原因引发异常,比如 my_object
未定义或在 my_object
实现中存在潜在的错误。 - Nick Dandoulakisisinstance('', Sequence) == True
),并且和任何序列一样,它是可迭代的(isinstance('', Iterable)
)。虽然 hasattr('', '__iter__') == False
,这可能会令人困惑。 - jfsmy_object
非常大(比如像itertools.count()
这样的无限对象),使用列表推导式将占用大量时间/内存。更好的方法是创建一个生成器,它永远不会尝试构建一个(潜在无限的)列表。 - Chris Lutzhasattr(u"hello", '__iter__')
返回True
。 - Carlostry:
iterator = iter(the_element)
except TypeError:
# not iterable
else:
# iterable
# for obj in iterator:
# pass
使用 抽象基类(Abstract Base Classes)。它们至少需要 Python 2.6,并且仅适用于新式类。
from collections.abc import Iterable # import directly from collections for Python < 3.3
if isinstance(the_element, Iterable):
# iterable
else:
# not iterable
然而,根据文档所述:iter()
更加可靠:
使用
isinstance(obj, Iterable)
可以检测到已注册为Iterable或具有__iter__()
方法的类,但无法检测使用__getitem__()
方法进行迭代的类。唯一确定对象是否可迭代的可靠方法是调用iter(obj)
。
PyUNO
中的一个bug。请注意你的错误信息写成了issubclass()
而不是isinstance()
。 - Georg Schölly我希望能更详细地介绍 iter
、__iter__
和 __getitem__
之间的相互作用,以及在幕后发生了什么。掌握了这些知识,您将能够理解为什么最好的选择只能是:
try:
iter(maybe_iterable)
print('iteration will probably work')
except TypeError:
print('not iterable')
首先列出事实,然后简要提醒您在Python中使用for
循环时会发生什么,最后讨论以阐明这些事实。
通过调用iter(o)
可以从任何对象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__
。
如果您想使自己的对象可迭代,请始终实现__iter__
方法。
for
循环为了跟随本文,您需要了解在Python中使用for
循环时会发生什么。如果您已经了解,请直接跳到下一节。
使用 for item in o
遍历可迭代对象 o
时,Python 会调用 iter(o)
并期望返回一个迭代器对象。迭代器是任何实现了 __next__
方法(在 Python 2 中为 next
方法)和 __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
关于获取迭代器和不可靠的检查:
考虑以下类:
class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
当使用 BasicIterable
的实例调用 iter
时,会返回一个迭代器而没有任何问题,因为 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
这就是为什么Luciano Ramalho在他的书《流畅的Python》中建议调用iter
并处理可能出现的TypeError
作为检查对象是否可迭代的最准确方法。直接引用该书的原话:
从Python 3.4开始,检查一个对象
x
是否可迭代的最准确方法是调用iter(x)
并处理TypeError
异常(如果不可迭代)。这比使用isinstance(x, abc.Iterable)
更准确,因为iter(x)
还考虑到了旧的__getitem__
方法,而Iterable
ABC则没有。
关于第三点:迭代仅提供__getitem__
但不提供__iter__
的对象
对BasicIterable
实例进行迭代时会按预期工作:Python构建一个迭代器,尝试从零开始获取项目,直到IndexError
被触发。演示对象的__getitem__
方法只需返回由iter
返回的迭代器提供给__getitem__(self, item)
的item
。
>>> 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
异常,而针对item==3
引发的IndexError
异常则在内部处理。这就是为什么使用for
循环遍历BasicIterable
可以按预期工作的原因:
>>> 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__
方法:
当对一个对象o
调用iter(o)
时,iter
会确保如果方法__iter__
存在,则返回值为一个迭代器。这意味着返回的对象必须实现__next__
(在Python 2中是next
)和__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
第七点要点:您的可迭代类应实现__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__
的存在是为了向后兼容。引用自《流畅的 Python》:这就是为什么任何 Python 序列都是可迭代的: 它们都实现了
__getitem__
。事实上, 标准序列也实现了__iter__
,你的也应该这样做,因为特殊处理__getitem__
存在于向后兼容性原因,并且在未来可能被删除 (尽管在我写作本文时并未被弃用)。
try
块中返回 True
,并在 except TypeError
块中返回 False
,定义一个谓词 is_iterable
是安全的吗? - alancalvitti我最近一直在研究这个问题。基于此,我的结论是,现在这是最好的方法:
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'
如果我们使用 isinstance()
,就不会错误地认为 Faker 实例(或任何只有 __getitem__
的其他对象)是可迭代的:>>> from collections.abc import Iterable
>>> from faker import Faker
>>> isinstance(Faker(), Iterable)
False
之前的回答提到使用iter()
更安全,因为Python中实现迭代的旧方法是基于__getitem__
,而isinstance()
方法无法检测到这一点。也许在旧版本的Python中确实如此,但根据我的广泛测试,isinstance()
在现在工作得非常好。唯一的情况是UserDict
在使用Python 2时isinstance()
无法正常工作,但可以使用isinstance(item, (Iterable, UserDict))
来解决这个问题。
typing.Dict
被 iter(Dict)
视为可迭代对象,但是 list(Dict)
会出现错误 TypeError: Parameters to generic types must be types. Got 0.
。如预期的那样,isinstance(Dict, Iterable)
返回 false。 - Pekka Klärckiter
导致我们的一些代码在使用“预缓存”时不必要地减慢了速度。如果 __iter__
代码很慢,那么每次只想查看某个东西是否可迭代时调用 iter
的速度也会变慢。 - thorwhalenisinstance(obj, Iterable)
失败:numpy 的单个值 'arrays'。如果你有 obj = np.array(int(1))
,numpy 会高兴地说 obj = array(1)
。形状是一个空元组,而 len(obj)
返回 TypeError: len() of unsized object
。然而!如果你写:isinstance(obj, Iterable)
,你会得到...True
。 - physincubus自从Python 3.5版本起,您可以使用标准库中的typing模块来处理类型相关的事情:
from typing import Iterable
...
if isinstance(my_item, Iterable):
print(True)
typing
模块是用于类型检查工具(如 MyPy, PyCharm)的,不能保证此行为。我认为你应该从 collections.abc
模块中导入 Iterable
类。 - c z这还不够:被__iter__
返回的对象必须实现迭代协议(即next
方法)。请参阅文档中相关部分。
在Python中,一个好的实践是“试一试”而不是“检查”。
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基础设施非常有用。
except:
,这会促进不良实践。 - jfstry:
#treat object as iterable
except TypeError, e:
#object is not actually iterable
不要运行检查来判断你的鸭子是否真的是一只鸭子,而应该将其视为可迭代的,并在它不可迭代时报错。
TypeError
异常并使你失去控制,但基本上是这样的。 - Chris LutzTypeError
后添加 "as e
" 而不是添加 ", e
" 来捕获吗? - HelloGoodbye你可以尝试这样做:
def iterable(a):
try:
(x for x in a)
return True
except TypeError:
return False
如果我们可以创建一个生成器来迭代它(但从不使用该生成器,以便它不占用空间),那么它就是可迭代的。这似乎是“当然”的事情。那么为什么你需要首先确定一个变量是否可迭代呢?
iterable(itertools.repeat(0))
怎么样? :) - badp(x for x in a)
只是创建了一个生成器,它并没有在a上进行任何迭代。 - catchmeifyoutry(x for x in a)
和尝试iterator = iter(a)
两者是否完全等效?或者有一些情况两者是不同的吗? - max到目前为止,我找到的最佳解决方案是:
hasattr(obj, '__contains__')
它基本上检查对象是否实现了in
运算符。
优点(其他解决方案都没有全部包括):
__iter__
相反)注:
__getitem__
方法足以使对象可迭代。 - KosmyObj
,那么只要myObj
是字典实例,使用iter(myObj)
就会成功。这是一个微妙之处,如果您想知道什么是序列和什么不是序列很重要。(在Python 2中) - Ben Mosher__getitem__
方法也足以使对象可迭代。 - Carlos A. Gómez__getitem__
是可行的,但并非必需。例如,生成器对象是可迭代的,但它没有__getitem__
方法。 - Max__getitem__
是可行的,但并非必需。例如,生成器对象是可迭代的,但它没有__getitem__
方法。 - undefined