有没有类似的方法isiterable
?到目前为止,我发现的唯一解决方案是致电
hasattr(myObj, '__iter__')
但我不确定这是多么万无一失。
检查__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 词汇表:
Pythonic 编程风格,通过检查对象的方法或属性签名而不是通过与某个类型对象的显式关系来确定对象的类型(“如果它看起来像鸭子,叫起来像鸭子,它一定是鸭子。”)通过强调接口精心设计的代码不是特定类型,而是通过允许多态替换来提高其灵活性。Duck-typing 避免了使用 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(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)
.
我想更深入地了解 之间的相互作用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__
检查 or 的实例Iterable
或Sequence
检查属性__iter__
是不够的。
如果一个对象o
实现了 only __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
)。然后 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
迭代 a 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
or的实例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
异常(如果不是)。这比 using 更准确isinstance(x, abc.Iterable)
,因为iter(x)
它还考虑了遗留__getitem__
方法,而Iterable
ABC 没有。
第 3 点:迭代仅提供__getitem__
但不提供的对象__iter__
按预期迭代工作实例BasicIterable
:Python 构造了一个迭代器,它尝试按索引获取项目,从零开始,直到IndexError
引发 an。演示对象的__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
内容在内部处理。这就是为什么在 aBasicIterable
上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
会成功,但会在第一次调用__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__
to的调用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__
出于向后兼容性的原因,存在的特殊处理可能会在未来消失(尽管在我写这篇文章时它并没有被弃用)。
我最近一直在研究这个问题。基于此,我的结论是,现在这是最好的方法:
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))
来覆盖它。
从Python 3.5开始,您可以使用标准库中的类型模块来处理类型相关的事情:
from typing import Iterable
...
if isinstance(my_item, Iterable):
print(True)
这还不够:返回的对象__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 会自动将该类的对象识别为可迭代对象。
所以,如果你只是期望一个可迭代的,迭代它并忘记它。另一方面,如果您需要根据输入类型执行不同的操作,您可能会发现 ABC 基础架构非常有用。
try:
#treat object as iterable
except TypeError, e:
#object is not actually iterable
不要检查你的鸭子是否真的是一只鸭子,看看它是否是可迭代的,把它当作是,如果不是就抱怨。
你可以试试这个:
def iterable(a):
try:
(x for x in a)
return True
except TypeError:
return False
如果我们可以创建一个迭代它的生成器(但永远不要使用生成器,这样它就不会占用空间),它是可迭代的。似乎是“duh”之类的东西。为什么首先需要确定变量是否可迭代?
到目前为止我找到的最佳解决方案:
hasattr(obj, '__contains__')
它基本上检查对象是否实现了in
运算符。
优点(其他解决方案都没有这三个):
__iter__
)笔记:
我在这里找到了一个不错的解决方案:
isiterable = lambda obj: isinstance(obj, basestring) \
or getattr(obj, '__iter__', False)
根据Python 2 Glossary,可迭代对象是
所有序列类型(例如
list
、str
和tuple
)和一些非序列类型,例如dict
and以及您使用or方法file
定义的任何类的对象。Iterables 可用于 for 循环和许多其他需要序列的地方(zip()、map()、...)。当可迭代对象作为参数传递给内置函数 iter() 时,它会返回该对象的迭代器。__iter__()
__getitem__()
当然,鉴于 Python 的一般编码风格基于“请求宽恕比许可更容易”这一事实,一般的期望是使用
try:
for i in object_in_question:
do_something
except TypeError:
do_something_for_non_iterable
但是如果你需要明确地检查它,你可以测试一个 iterable by hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__")
。您需要检查两者,因为str
s 没有__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
函数所做的那样
编辑:如果你安装了 numpy,你可以简单地做: from numpy import iterable
,这就像
def iterable(obj):
try: iter(obj)
except: return False
return True
如果你没有 numpy,你可以简单地实现这个代码,或者上面的那个。
pandas有一个这样的内置函数:
from pandas.util.testing import isiterable
我总是不明白为什么 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 对象)
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 中说否(无论是''
orb''
还是u''
)。感谢@LuisMasuelli 注意到它也会让你在一辆越野车上失望__iter__
。
有很多方法可以检查对象是否可迭代:
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")
尊重 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
如果我也可以依靠鸭子打字来引发一个没有为我的对象定义的 if 的检查,那么存在AttributeError
如何检查__call__
,但可迭代检查不是这种情况?
我不知道答案,但是您可以实现我(和其他用户)提供的函数,或者只是在代码中捕获异常(您在该部分的实现将与我编写的函数一样 - 只需确保隔离从其余代码中创建迭代器,以便您可以捕获异常并将其与另一个TypeError
.
如果 object 是可迭代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
__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
该对象非常有用。)
不是真的“正确”,但可以快速检查最常见的类型,如字符串、元组、浮点数等......
>>> '__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
派对有点晚了,但我问自己这个问题,看到这个然后想到了一个答案。不知道有没有人发过这个。但本质上,我注意到所有可迭代类型的字典中都有__getitem__()。这就是您无需尝试即可检查对象是否为可迭代对象的方式。(双关语)
def is_attr(arg):
return '__getitem__' in dir(arg)