在Python中,如何确定对象是否可迭代?

有没有一种方法可以isiterable ? 我目前find的唯一解决scheme就是打电话

 hasattr(myObj, '__iter__') 

但我不确定这是多么的愚蠢。

  1. 检查__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__方法。

  2. 另一个普遍的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' 
  3. 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循环时会发生什么情况,然后通过讨论来说明事实。

事实

  1. 如果以下至less一个条件成立,则可以通过调用iter(o)从任何对象o获取迭代器:

    a) o有一个返回一个迭代器对象的__iter__方法。 迭代器是带有__iter____next__ (Python 2: next )方法的任何对象。

    b) o有一个__getitem__方法。

  2. 检查IterableSequence的实例,或检查属性__iter__是不够的。

  3. 如果一个对象o只实现了__getitem__ ,而不是__iter__iter(o)将构造一个迭代器,它试图从索引0开始从整数索引中获取项目。迭代器将捕获任何IndexError (但是没有其他错误)然后提出StopIteration本身。

  4. 从最一般意义上说,没有办法检查iter返回的迭代器是否理智,而不是尝试一下。

  5. 如果一个对象o实现了__iter__iter函数将确保__iter__返回的对象是一个迭代器。 没有理智的检查,如果一个对象只实现__getitem__

  6. __iter__胜。 如果一个对象o实现了__iter____getitem__ __iter__iter(o)将会调用__iter__

  7. 如果你想让你自己的对象迭代,总是实现__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__属性并且不被认为是IterableSequence一个实例是很重要的:

 >>> 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) ,如果不是,则处理TypeErrorexception。 这比使用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不从dictinheritance,这意味着实例不会有__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)被调用一个对象oiter会确保__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__的原因如下:

  1. 如果你实现__iter__ ,实例将被认为是可迭代的, isinstance(o, collections.Iterable)将返回True
  2. 如果__iter__返回的对象不是迭代器,则iter将立即失败并引发TypeError
  3. __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(如liststrtuple )以及一些非序列types(如dictfile以及用__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()) 

备注

  1. 如果exceptiontypes是相同的,那么这个对象是不是可迭代的,或者已经实现了一个错误的__iter__是无关紧要的:无论如何,你将不能迭代对象。
  2. 我想我理解你的担忧:如果我可以依靠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()会对bytesbytearraytypes的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__