Python:如何确定一个对象是否可迭代?
- 2024-12-03 08:45:00
- admin 原创
- 146
问题描述:
有没有类似的方法isiterable
?到目前为止,我发现的唯一解决方案是调用:
hasattr(myObj, '__iter__')
但我不确定这是否万无一失。
解决方案 1:
检查
__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 词汇表:
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(the_element)
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(the_element, Iterable):
# iterable
else:
# not iterable
但是,正如文档中iter()
所述,它更可靠一些:
检查会
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
通过调用从任何对象获取迭代器: a)具有返回迭代器对象的方法。迭代器是具有和( Python 2: ) 方法的任何对象。b )具有方法。iter(o)
o
`__iter____iter__
next`next
o
`__getitem__`
Iterable
检查或的实例Sequence
,或者检查属性__iter__
是不够的。如果一个对象
o
只实现了__getitem__
,但没有实现__iter__
,则会构造一个迭代器,尝试从整数索引(从索引 0 开始)iter(o)
获取项目。迭代器将捕获引发的任何错误(但没有其他错误),然后引发自身。o
`IndexError`StopIteration
iter
从最普遍的意义上讲,除了尝试一下之外,没有其他方法可以检查返回的迭代器是否合理。如果对象
o
实现了__iter__
,则该iter
函数将确保 返回的对象__iter__
是迭代器。如果对象仅实现了 ,则不会进行健全性检查__getitem__
。__iter__
获胜。如果一个对象o
同时实现了__iter__
和__getitem__
,iter(o)
则将调用__iter__
。如果您想让自己的对象可迭代,请始终实现该
__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
这就是为什么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
循环遍历 a可以按预期工作的原因: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__
如下:
如果您实现
__iter__
,实例将被视为可迭代,并将isinstance(o, collections.abc.Iterable)
返回True
。如果返回的对象
__iter__
不是迭代器,iter
将立即失败并引发TypeError
。出于向后兼容的原因,对 进行了特殊处理
__getitem__
。再次引用 Fluent Python 的话:
这就是为什么任何 Python 序列都是可迭代的:它们都实现了
__getitem__
。事实上,标准序列也实现了__iter__
,你的序列也应该实现 ,因为 的特殊处理是__getitem__
出于向后兼容的原因而存在的,将来可能会消失(尽管在我写这篇文章时它还没有被弃用)。
解决方案 4:
我最近一直在研究这个问题。基于此,我的结论是,目前这是最好的方法:
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__
存在正当理由,而上面的代码无法很好地工作。作为一个现实生活中的例子,我们可以使用Faker。上面的代码报告它是可迭代的,但实际上尝试迭代它会导致(使用 Faker 4.0.2 测试):__getitem__
`AttributeError`
>>> 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))
来解决这个问题。
解决方案 5:
从Python 3.5开始,你可以使用标准库中的typing模块来处理类型相关的操作:
from typing import Iterable
...
if isinstance(my_item, Iterable):
print(True)
解决方案 6:
这还不够:返回的对象__iter__
必须实现迭代协议(即方法)。请参阅文档next
中的相关部分。
在 Python 中,一个好的做法是“尝试并看看”而不是“检查”。
解决方案 7:
在 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 基础结构非常有用。
解决方案 8:
try:
#treat object as iterable
except TypeError, e:
#object is not actually iterable
不要检查你的鸭子是否真的是鸭子,看看它是否可迭代,而要像对待可迭代对象一样对待它,如果不是,则抱怨。
解决方案 9:
你可以尝试这个:
def iterable(a):
try:
(x for x in a)
return True
except TypeError:
return False
如果我们可以创建一个迭代它的生成器(但从不使用该生成器,这样它就不会占用空间),它就是可迭代的。这似乎是“废话”。为什么首先需要确定变量是否可迭代?
解决方案 10:
到目前为止我发现的最佳解决方案:
hasattr(obj, '__contains__')
它主要检查对象是否实现了in
操作符。
优点(其他解决方案均不具备这三个优点):
它是一个表达式(作为lambda工作,而不是try...except变体)
它(应该)由所有可迭代对象实现,包括字符串(而不是
__iter__
)适用于任何 Python >= 2.5
笔记:
Python 的“请求原谅,而不是请求许可”的哲学在以下情况下并不适用:例如,在一个列表中,你同时拥有可迭代对象和不可迭代对象,并且你需要根据其类型对每个元素进行不同的处理(在 try 上处理可迭代对象,在 except 上处理不可迭代对象是可行的,但看起来非常丑陋且具有误导性)
这个问题的解决方案是尝试实际迭代对象(例如 [x for x in obj])来检查它是否可迭代,这可能会对大型迭代器造成严重的性能损失(特别是如果你只需要迭代器的前几个元素),应该避免
解决方案 11:
我在这里找到了一个很好的解决方案:
isiterable = lambda obj: isinstance(obj, basestring) \n or getattr(obj, '__iter__', False)
解决方案 12:
根据Python 2 词汇表,可迭代对象是
所有序列类型(例如
list
、str
和tuple
)和一些非序列类型,例如dict
和file
以及使用__iter__()
or__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
s 没有__iter__
方法(至少在 Python 2 中没有,在 Python 3 中有),并且因为generator
对象没有方法__getitem__
。
解决方案 13:
我经常发现在脚本中定义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,您可以简单地实现此代码或上面的代码。
解决方案 14:
熊猫有一个内置函数如下:
from pandas.util.testing import isiterable
解决方案 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 中的对象是可迭代的,True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))
Python 2 中没有这种类型。bytearray
Python 2 和 3 中的对象是可迭代的True == is_iterable(bytearray(b"abc"))
OPhasattr(x, '__iter__')
方法将对 Python 3 中的字符串说是,对 Python 2 中的字符串说否(无论是否''
或b''
或u''
)。感谢 @LuisMasuelli 注意到它还会让您对有缺陷的 感到失望__iter__
。
解决方案 16:
我一直不明白为什么 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 对象)
解决方案 17:
有很多方法可以检查对象是否可迭代:
from collections.abc import Iterable
myobject = 'Roster'
if isinstance(myobject , Iterable):
print(f"{myobject } is iterable")
else:
print(f"strong text{myobject } is not iterable")
解决方案 18:
最简单的方式,尊重 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__
是否已经实现了错误是无关紧要的:无论如何您都无法迭代该对象。我想我明白你的担心:如果我也可以依靠鸭子类型来引发我的对象的if is not defined ,那么如何
callable
检查 exist ?但对于可迭代检查来说情况并非如此?AttributeError
`__call__`
我不知道答案,但您可以实现我(和其他用户)给出的函数,或者只是在您的代码中捕获异常(您在那部分的实现将像我编写的函数 - 只需确保将迭代器创建与其余代码隔离开来,这样您就可以捕获异常并将其与另一个区分开来TypeError
。
解决方案 19:
如果对象是可迭代的,则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
解决方案 20:
在我的代码中我曾经检查不可迭代的对象:
hasattr(myobject,'__trunc__')
这非常快,并且也可以用来检查可迭代对象(使用not
)。
我不能 100% 确定此解决方案是否适用于所有对象,也许其他人可以提供更多背景信息。方法似乎与数值类型有关(所有可以四舍五入为整数的对象都需要它)。但我没有找到任何包含或的__trunc__
对象。__trunc__
`__iter__`__getitem__
解决方案 21:
__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
该对象变得有用。)
解决方案 22:
并不是真正“正确”,但可以快速检查大多数常见类型,如字符串、元组、浮点数等......
>>> '__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
解决方案 23:
有点迟了,但我问了自己这个问题,然后看到了这个,然后想到了一个答案。我不知道是否有人已经发布了这个。但本质上,我注意到所有可迭代类型的字典中都有__getitem__()。这就是你无需尝试就可以检查对象是否是可迭代对象的方法。(双关语)
def is_attr(arg):
return '__getitem__' in dir(arg)
解决方案 24:
在尝试了各种函数之后,我相信这是可靠地判断对象是否可迭代的最快方法,尽管它涉及到一些例外:
def isiterable(x):
if hasattr(x, '__iter__'): return not isinstance(x, type)
if hasattr(x, '__getitem__'):
try:
x[0]
return True
except Exception:
return False
return False
这不是一个完美的解决方案,但正如我下面所解释的那样,这是最可靠的解决方案之一。我能想到的两种极端情况是此函数会失败:第一种是具有__getitem__
方法和的不可迭代对象x[0]
。第二种是可迭代type
对象。
如果您不想要裸异常,下面我描述了几种方法,它们都有不同的缺点。
哪些内容可以迭代
为了可迭代,对象需要__getitem__
或__iter__
方法。
但是有些对象__getitem__
是不可迭代的,例如:
class Getitem_ButNotIterable:
def __init__(self): self.dict_ = {"a":1, "b":2}
def __getitem__(self, item:str): return self.dict_[item]
for i in Getitem_ButNotIterable():
pass
>> KeyError: 0
值得一提的是,除非您实际尝试对其进行迭代,否则运行iter(Getitem_ButNotIterable())
不会引发异常,因此该方法并不完全可靠。
类型可以具有__iter__
属性,但它们(通常)也不可迭代。
hasattr(list, "__iter__") >> True
for i in list: pass >> TypeError: 'type' object is not iterable
然而类型实际上是可以迭代的,也就是说如果它们的元类有__iter__
方法的话。
最困难的情况是这样的:
class Getitem_ButNotIterable2:
def __init__(self): self.dict_ = {0:1, "b":2}
def __getitem__(self, item:str): return self.dict_[item]
如果您尝试迭代它,它将调用x[0]
which 返回 0,但随后它将调用x[1]
which 引发KeyError,而 iter 不会捕获该错误。不幸的是,我认为没有简单的方法来检测这些错误。
测试对象是否可迭代的方法
为了测试各种方法,我创建了 40 个不同的可迭代对象和不可迭代对象,并在每个可迭代对象/不可迭代对象上尝试了 1_000_000 次函数,并写下了所有操作所花费的总时间。开头的方法需要 8 秒作为参考。
from collections.abc import Iterable
def is_iterable1(x):
return isinstance(x, Iterable)
耗时 17 秒。这将检查对象是否有__iter__
方法,因此它将无法识别仅有的可迭代对象__getitem__
。
值得一提的是,如果您使用已弃用的版本typing.Iterable
,时间将增加到 50 秒。
def is_iterable2(x):
return hasattr(x, "__iter__")
耗时 5 秒。这几乎相当于isinstance(x, Iterable)
,但是还有额外的误报:类型,如is_iterable2(list)
将返回 True。
def is_iterable3(x):
return hasattr(x, "__iter__") and hasattr(x, "__getitem__")
耗时 7 秒。与上述两种方法不同,该方法可以识别可迭代对象,但也会对不可迭代对象和使用这些方法的类型__getitem__
给出误报__getitem__
def is_iterable4(x):
try:
iter(x)
return True
except TypeError:
return False
return True
耗时 36 秒。这将对具有 的不可迭代对象产生误报__getitem__
。更改iter(x)
为(i for i in x)
是等效的。如果您不想要裸露的异常,这是一种好方法。
def is_iterable5(x):
try:
0 in x
except TypeError:
return False
else:
return True
耗时 50 秒。这将对具有 的不可迭代对象引发异常__getitem__
。
def is_iterable6(x):
try:
for _ in x: return True
except Exception: return False
return True
耗时 33 秒。我们现在遇到了一些例外,但我想不出任何其他方法可以让它更可靠。可能存在一些极端情况,在这些情况下,您不想在通常用途之外迭代某些对象,因为这可能会扰乱内部状态。
def is_iterable7(x):
if hasattr(x.__class__, '__iter__'): return True
if hasattr(x, '__getitem__'):
try:
x[0]
return True
except Exception:
return False
return False
耗时 23 秒。几乎可以正确评估所有内容,因为它不会对具有__iter__
方法的类型给出真否定。仍然有两种边缘情况:具有的不可迭代对象__getitem__
和具有具有__iter__
方法的元类的可迭代类。但是,我认为不可能比这更可靠,
def is_iterable8(x):
if hasattr(x, '__iter__'): return not isinstance(x, type)
if hasattr(x, '__getitem__'):
try:
x[0]
return True
except Exception:
return False
return False
耗时 8 秒。应该相当于上面的函数,所以,除非您期望带有元类的类__iter__
,并且您不介意裸异常,否则这似乎是判断对象是否可迭代的最佳方法。
奖金:
def super_slow_but_is_almost_definitely_iterable(x):
try:
for i,v in enumerate(x):
if i == 1000:break
return True
except Exception: return False
令人惊讶的是,只花了 40 秒。这是最可靠的方法,但它涉及迭代,因此在某些极端情况下会弄乱内部状态。此外,如果您有迭代成本高昂的对象,这将变得非常慢。
解决方案 25:
也许只是写hasattr(obj, "__iter__")
或者...类似这样的方法可能会有效:
def is_iterable(obj: object) -> bool:
return hasattr(obj, "__iter__")