问题:在Python中,如何确定对象是否可迭代?
有没有类似的方法isiterable
?到目前为止,我发现的唯一解决方案是调用
hasattr(myObj, '__iter__')
但是我不确定这有多愚蠢。
回答 0
我最近一直在研究这个问题。基于此,我的结论是,如今这是最好的方法:
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
使用Python2。如果相关,则可以使用isinstance(item, (Iterable, UserDict))
它进行覆盖。
回答 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__
方法。另一种通用的pythonic方法是假定一个可迭代的对象,如果它不适用于给定的对象,则将优雅地失败。Python词汇表:
通过检查对象的方法或属性签名而不是通过与某种类型对象的显式关系来确定对象类型的Python编程风格(“如果它看起来像鸭子,并且像鸭子一样嘎嘎叫,那一定是鸭子。”)通过强调接口经过精心设计的代码(而不是特定类型)通过允许多态替换来提高其灵活性。鸭式输入避免使用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__
。
回答 2
鸭打字
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
检查
isinstance(obj, Iterable)
会检测已注册为Iterable或具有__iter__()
方法的类,但不会检测到使用该__getitem__()
方法进行迭代的类。确定对象是否可迭代的唯一可靠方法是调用iter(obj)
。
回答 3
我想摆脱一点点的相互作用越轻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__
and__getitem__
,iter(o)
将调用__iter__
。如果要使自己的对象可迭代,请始终实现该
__iter__
方法。
for
循环
为了继续学习,您需要了解for
在Python中使用循环时会发生什么。如果您已经知道,请随时跳到下一部分。
当for item in o
用于某些可迭代对象时o
,Python调用iter(o)
并期望将迭代器对象作为返回值。迭代器是实现__next__
(或next
在Python 2中)方法和__iter__
方法的任何对象。
按照约定,__iter__
迭代器的方法应返回对象本身(即return self
)。然后next
,Python调用迭代器,直到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
Implements __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的Fluent Python建议调用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
注意,将to __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
,但会在第一次调用时引发Exception __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__
and __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__
出于向后兼容的原因而存在对的特殊处理,并且可能在将来消失(尽管在我撰写本文时不推荐使用)。
回答 4
这还不够:返回的对象__iter__
必须实现迭代协议(即next
方法)。请参阅文档中的相关部分。
在Python中,一个好的做法是“尝试一下”而不是“检查”。
回答 5
在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)
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基础结构非常有用。
回答 6
try:
#treat object as iterable
except TypeError, e:
#object is not actually iterable
不要运行检查以查看您的鸭子是否真的是鸭子,以查看它是否可迭代,请像对待鸭子一样对待它,否则请抱怨。
回答 7
从Python 3.5开始,您可以使用标准库中的类型输入模块来处理与类型相关的事情:
from typing import Iterable
...
if isinstance(my_item, Iterable):
print(True)
回答 8
到目前为止,我发现的最佳解决方案是:
hasattr(obj, '__contains__')
基本上检查对象是否实现了in
运算符。
优点(其他解决方案都没有这三个优点):
- 它是一个表达式(作为lambda,而不是try … except变体)
- (应该)由所有可迭代对象(包括字符串)实现(而不是
__iter__
) - 适用于任何Python> = 2.5
笔记:
- 例如,在列表中同时具有可迭代和不可迭代,并且您需要根据其类型对每个元素进行不同处理(在try和non-上处理可迭代)时,Python的“请求宽恕,而不是允许”的哲学无法很好地工作。上的iterables 会起作用,但是看起来很难看并且具有误导性)
- 尝试实际迭代对象(例如[x for obj中的x的x])以检查其是否可迭代的问题的解决方案,可能会导致大型可迭代对象的性能下降(尤其是如果您只需要可迭代对象的前几个元素,则为示例),应避免
回答 9
您可以尝试以下方法:
def iterable(a):
try:
(x for x in a)
return True
except TypeError:
return False
如果我们可以使生成器在其上进行迭代(但不要使用生成器,这样就不会占用空间),那么它是可迭代的。好像是“ duh”之类的东西。为什么首先需要确定变量是否可迭代?
回答 10
我在这里找到了一个不错的解决方案:
isiterable = lambda obj: isinstance(obj, basestring) \
or getattr(obj, '__iter__', False)
回答 11
根据Python 2词汇表,可迭代项是
所有序列类型(如
list
,str
,和tuple
)和一些非序列类型,如dict
和file
以及你与定义任何类的对象__iter__()
或__getitem__()
方法。Iterables可用于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
s没有__iter__
方法(至少在Python 2中没有),并且generator
对象没有__getitem__
方法。
回答 12
我经常在脚本内找到定义iterable
函数的方便方法。(现在结合了Alfe建议的简化):
import collections
def iterable(obj):
return isinstance(obj, collections.Iterable):
因此,您可以以可读性强的形式测试任何对象是否可迭代
if iterable(obj):
# act on iterable
else:
# not iterable
就像您使用该callable
功能一样
编辑:如果您已安装numpy,则可以执行以下操作:from numpy import iterable
,这就像
def iterable(obj):
try: iter(obj)
except: return False
return True
如果您没有numpy,则可以简单地实现此代码或上面的代码。
回答 13
大熊猫 具有这样的内置功能:
from pandas.util.testing import isiterable
回答 14
它总是躲避我,为什么Python有callable(obj) -> bool
,但不是iterable(obj) -> bool
……
当然这是容易做到hasattr(obj,'__call__')
,即使是速度较慢。
由于几乎所有其他答案都建议使用try
/ except TypeError
,因此在任何语言中通常都将异常测试视为不良实践,因此,以下是iterable(obj) -> bool
我越来越喜欢并经常使用的实现:
为了python 2的缘故,我将只使用lambda来提高性能……
(在python 3中,定义函数的功能def
与无关紧要lambda
)
iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')
请注意,此功能对于的对象执行得更快,__iter__
因为它不会测试__getitem__
。
大多数可迭代对象都应依赖于__iter__
特殊情况对象回退到的位置__getitem__
,尽管要使对象可迭代则需要使用任一个。
(由于这是标准的,因此也会影响C对象)
回答 15
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中的对象是可迭代的。Python2True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))
中没有此类。bytearray
Python 2和3中的对象是可迭代的True == is_iterable(bytearray(b"abc"))
该任择议定书hasattr(x, '__iter__')
的做法将是说在Python 3,没有在Python 2串(也罢''
或b''
或u''
)。感谢@LuisMasuelli注意到它也会让您失望__iter__
。
回答 16
尊重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())
注意事项:
__iter__
如果异常类型相同,则对象是不可迭代的还是已实现越野车的区分是无关紧要的:无论如何,您将无法迭代该对象。我想我理解您的担心:
callable
如果我还可以依赖于鸭子类型来引发未为我的对象定义的AttributeError
if 的检查,那么它如何存在__call__
,但可迭代检查不是这种情况?我不知道答案,但是您可以实现我(和其他用户)提供的功能,也可以仅捕获代码中的异常(您在该部分中的实现将类似于我编写的功能-只要确保隔离从代码的其余部分创建迭代器,这样您可以捕获异常并将其与另一个区别
TypeError
。
回答 17
如果对象是可迭代的isiterable
,则以下代码中的func返回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
回答 18
除了检查__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
— utdemir接受的答案
(这对于检查是否可以调用len
该对象很有用。)
回答 19
并不是真正的“正确”,但可以用作最常见类型的快速检查,例如字符串,元组,浮点数等。
>>> '__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
回答 20
Kinda参加聚会很晚,但是我问了自己这个问题,然后看到了这个答案。我不知道是否有人已经发布了这个。但从本质上讲,我注意到所有可迭代类型的字典中都具有__getitem __()。这是您无需尝试即可检查对象是否可迭代的方式。(双关语意)
def is_attr(arg):
return '__getitem__' in dir(arg)