在Python中,如何确定对象是否可迭代?
有没有一种方法可以isiterable
? 我目前find的唯一解决scheme就是打电话
hasattr(myObj, '__iter__')
但我不确定这是多么的愚蠢。
-
检查
__iter__
适用于序列types,但在Python 2中 ,例如string会失败。 我想知道正确的答案,在那之前,这里有一个可能性(也可以在string上工作):try: some_object_iterator = iter(some_object) except TypeError, te: print some_object, 'is not iterable'
iter
内置检查__iter__
方法或string__getitem__
方法。 -
另一个普遍的pythonic方法是假定一个iterable,然后优雅的失败,如果它不适用于给定的对象。 Python词汇表:
Pythonic编程风格通过检查它的方法或属性签名来确定一个对象的types,而不是通过与某个types对象的显式关系来判断对象的types(“如果它看起来像一只鸭子 ,像一只鸭子 ,它肯定是一只鸭子 。”)通过强调接口精心devise的代码通过允许多态replace来提高其灵活性,而不是特定的types。 鸭式input避免了使用type()或isinstance()的testing。 相反,它通常使用EAFP(容易提出宽恕而不是权限)编程风格。
…
try: _ = (e for e in my_object) except TypeError: print my_object, 'is not iterable'
-
collections
模块提供了一些抽象的基类,它们允许询问类或实例是否提供特定的function,例如:import collections if isinstance(e, collections.Iterable): # e is iterable
但是,这不会检查可通过
__getitem__
进行迭代的类。
鸭子打字
try: iterator = iter(theElement) except TypeError: # not iterable else: # iterable # for obj in iterator: # pass
types检查
使用抽象基类 。 他们至less需要Python 2.6,只能用于新式类。
import collections if isinstance(theElement, collections.Iterable): # iterable else: # not iterable
但是, collections.Iterable
不会检查__getitem__
,所以它更可靠。
这是不够的: __iter__
返回的对象必须实现迭代协议(即next
方法)。 请参阅文档中的相关部分。
在Python中,一个好的做法是“试试看”而不是“检查”。
我想多谈一些关于iter
, __iter__
和__getitem__
的相互作用以及窗帘后面发生的事情。 有了这些知识,你就能明白为什么你能做的最好
try: iter(maybe_iterable) print('iteration will probably work') except TypeError: print('not iterable')
我将首先列出事实,然后快速提醒您在Python中使用for
循环时会发生什么情况,然后通过讨论来说明事实。
事实
-
如果以下至less一个条件成立,则可以通过调用
iter(o)
从任何对象o
获取迭代器:a)
o
有一个返回一个迭代器对象的__iter__
方法。 迭代器是带有__iter__
和__next__
(Python 2:next
)方法的任何对象。b)
o
有一个__getitem__
方法。 -
检查
Iterable
或Sequence
的实例,或检查属性__iter__
是不够的。 -
如果一个对象
o
只实现了__getitem__
,而不是__iter__
,iter(o)
将构造一个迭代器,它试图从索引0开始从整数索引中获取项目。迭代器将捕获任何IndexError
(但是没有其他错误)然后提出StopIteration
本身。 -
从最一般意义上说,没有办法检查
iter
返回的迭代器是否理智,而不是尝试一下。 -
如果一个对象
o
实现了__iter__
,iter
函数将确保__iter__
返回的对象是一个迭代器。 没有理智的检查,如果一个对象只实现__getitem__
。 -
__iter__
胜。 如果一个对象o
实现了__iter__
和__getitem__
__iter__
,iter(o)
将会调用__iter__
。 -
如果你想让你自己的对象迭代,总是实现
__iter__
方法。
for
循环
为了跟随,你需要了解当你在Python中使用for
循环时会发生什么。 如果您已经知道,请随意跳到下一节。
当你使用for item in 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
讨论和插图
在第1和第2点:得到一个迭代器和不可靠的检查
考虑以下课程:
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的stream利Pythonbuild议调用iter
并处理潜在的TypeError
作为检查对象是否可迭代的最准确的方法。 直接从书中引用:
从Python 3.4开始,检查对象
x
是否可迭代的最准确的方法是调用iter(x)
,如果不是,则处理TypeError
exception。 这比使用isinstance(x, abc.Iterable)
更准确,因为iter(x)
也考虑遗留__getitem__
方法,而Iterable
ABC不考虑。
在第3点:遍历仅提供__getitem__
对象,而不是__iter__
对BasicIterable
的实例进行BasicIterable
可以像预期的BasicIterable
工作:Python构造一个迭代器,它试图通过索引从零开始提取项目,直到引发IndexError
。 演示对象的__getitem__
方法只是通过iter
返回的迭代器将作为参数提供给的item
返回给__getitem__(self, 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
inheritance,这意味着实例不会有__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__
(或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
构造迭代器FailGetItemIterable
成功,但会在第一次调用FailGetItemIterable
时抛出Exception。
>>> 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__
。 考虑下面的课
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__
你可能会问自己,为什么大多数内build序列像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.Iterable)
将返回True
。 - 如果
__iter__
返回的对象不是迭代器,则iter
将立即失败并引发TypeError
。 -
__getitem__
的特殊处理由于向后兼容性的原因而存在。 从Fluent Python再次引用:
这就是为什么Python序列是可迭代的:它们都实现
__getitem__
。 实际上,标准序列也实现了__iter__
,你也应该这样做,因为__getitem__
的特殊处理是为了向后兼容的原因而存在的,并且可能在将来消失(虽然我不写这个)。
try: #treat object as iterable except TypeError, e: #object is not actually iterable
不要运行检查,看看你的鸭子是否真的是一只鸭子 ,看看它是否可迭代,把它当作是对的,如果不是的话。
在Python <= 2.5中,你不能也不应该 – iterable是一个“非正式”的接口。
但是,由于Python 2.6和3.0,您可以利用新的ABC(抽象基类)基础结构以及集合模块中可用的一些内置ABCs:
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 – 它会在运行时引发一个exception。 因此,isinstance获得了“新”的含义 – 它只是检查“声明”types的兼容性,这是一个很好的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。
所以,如果你只是期望一个迭代,迭代它并忘记它。 另一方面,如果您需要根据inputtypes做不同的事情,您可能会发现ABC基础设施非常有用。
迄今为止我find的最佳解决scheme:
hasattr(obj, '__contains__')
基本上检查对象是否实现了in
运算符。
优点 (其他解决scheme都没有三个):
- 它是一个expression式(作为一个lambda ,而不是try … except变体)
- 它是(应该)由所有可迭代实现的,包括string (而不是
__iter__
) - 适用于任何Python> = 2.5
笔记:
- “请求原谅而不是权限”的Python哲学不能很好的工作,比如在一个列表中你既有可迭代对象也有非可迭代对象,并且你需要根据对象的types对每个对象进行不同的处理(在try和non-可以迭代除了会工作,但它会看起来屁股丑陋和误导)
- 这个试图对这个对象进行实际迭代的问题的解决scheme(例如,[x for obj])来检查它是否可迭代,可能会导致大的迭代次数产生显着的性能损失(特别是如果你只需要迭代的前几个元素,例如),应该避免
我在这里find了一个很好的解决
isiterable = lambda obj: isinstance(obj, basestring) \ or getattr(obj, '__iter__', False)
你可以试试这个:
def iterable(a): try: (x for x in a) return True except TypeError: return False
如果我们可以使一个生成器迭代它(但是从来不使用生成器,所以它不占用空间),它是可迭代的。 看起来像一个“呃”类的东西。 为什么你需要确定一个variables是否可迭代呢?
根据Python 2术语表 ,iterables是
所有的序列types(如
list
,str
和tuple
)以及一些非序列types(如dict
和file
以及用__iter__()
或__getitem__()
方法定义的任何类的对象。 Iterables可以在for循环中使用,也可以在需要序列的许多其他地方使用(zip(),map(),…)。 当一个可迭代对象作为parameter passing给内build函数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__")
来testing一个迭代。 你需要检查两者,因为str
没有__iter__
方法(至less不是在Python 2中,在Python 3中),因为generator
对象没有__getitem__
方法。
我经常在我的脚本中find方便的定义一个iterable
函数。 (现在纳入Alfebuild议的简化):
import collections def iterable(obj): return isinstance(obj, collections.Iterable):
所以你可以testing是否有任何对象是可读的forms迭代
if iterable(obj): # act on iterable else: # not iterable
就像你可以使用callable
函数一样
编辑:如果你有numpy安装,你可以简单地做:从numpy import iterable
,这是简单的东西
def iterable(obj): try: iter(obj) except: return False return True
如果你没有numpy,你可以简单的实现这个代码,或者上面的代码。
pandas有这样的内置function:
from pandas.util.testing import isiterable
最简单的方法,就是要知道Python的鸭子input是什么(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())
备注 :
- 如果exceptiontypes是相同的,那么这个对象是不是可迭代的,或者已经实现了一个错误的
__iter__
是无关紧要的:无论如何,你将不能迭代对象。 -
我想我理解你的担忧:如果我可以依靠duck打字来提高
AttributeError
如果__call__
没有为我的对象定义,但是迭代检查不是这样),那么callable
是如何存在的?我不知道答案,但是你可以实现我(和其他用户)给的函数,或者只是在你的代码中捕获exception(你在那部分的实现就像我写的函数 – 只要确保隔离从其他代码创build迭代器,以便捕获exception并将其与另一个
TypeError
区分开来。
如果对象是可迭代的,则下面代码中的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
def is_iterable(x): try: 0 in x except TypeError: return False else: return True
这将对所有的可迭代对象表示肯定的答案,但是它会在Python 2中对string表示“否” 。 (这就是我想要的,例如,当一个recursion函数可以接受一个string或一个string容器时,在这种情况下, 请求宽恕可能会导致obfuscode,而且最好首先获得许可。
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)
这里的许多其他策略会对string说“是”。 如果这是你想要的,使用它们。
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
types的string表示“是”。
- Python 3中的
bytes
对象是可迭代的True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))
在Python 2中没有这样的types。 - Python 2和3中的
bytearray
对象是可迭代的True == is_iterable(bytearray(b"abc"))
OP的hasattr(x, '__iter__')
方法会在Python 3中对string说yes,在Python 2中不对(不pipe是''
b''
还是“ u''
)。 感谢@LuisMasuelli注意到它也会让你失望的__iter__
。