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

有没有像isiterable这样的方法?到目前为止我找到的唯一解决方案是调用

hasattr(myObj, '__iter__')

但我不确定这有多愚蠢。

677466 次浏览
  1. 检查__iter__适用于序列类型,但它会在例如字符串在python2上失败。我也想知道正确的答案,在那之前,这里有一种可能性(也适用于字符串):

     try:some_object_iterator = iter(some_object)except TypeError as te:print(some_object, 'is not iterable')

iter内置检查__iter__方法,或者在字符串的情况下检查__getitem__方法。

  1. 另一种通用的pythonic方法是假设一个可迭代的,然后如果它不适用于给定对象,则优雅地失败。Python词汇表:

一种Pythonic编程风格,通过检查对象的方法或属性签名来确定对象的类型,而不是通过与某个类型对象的显式关系来确定对象的类型(“如果它看起来像鸭子,但看起来像鸭子,那它一定是鸭子。”)通过强调接口而不是特定类型,精心设计的代码通过允许多态替换来提高其灵活性。鸭子类型避免了使用type()或is实例()的测试。相反,它通常采用EAFP(比许可更容易请求原谅)风格的编程。

try:_ = (e for e in my_object)except TypeError:print my_object, 'is not iterable'
  1. #0模块提供了一些抽象基类,允许询问类或实例是否提供特定功能,例如:

     from collections.abc import Iterable
    if isinstance(e, Iterable):# e is iterable

但是,这不会检查可通过__getitem__迭代的类。

这还不够:__iter__返回的对象必须实现迭代协议(即next方法)。参见留档中的相关部分。

在Python中,一个好的做法是“尝试查看”而不是“检查”。

你可以试试这个:

def iterable(a):try:(x for x in a)return Trueexcept TypeError:return False

如果我们可以制作一个迭代它的生成器(但永远不要使用生成器,这样它就不会占用空间),它是可迭代的。看起来像是“废话”之类的事情。为什么你需要首先确定一个变量是否可迭代?

try:#treat object as iterableexcept TypeError, e:#object is not actually iterable

不要运行检查来查看如果你的鸭子真的是鸭子,看看它是否是可迭代的,把它当作是可迭代的,如果不是,就抱怨。

鸭子打字

try:iterator = iter(the_element)except TypeError:# not iterableelse:# 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):# iterableelse:# not iterable

但是,iter()更可靠,如留档的所述:

检查isinstance(obj, Iterable)检测以下类注册为Iterable或具有__iter__()方法,但是它不会检测使用__getitem__()迭代的类方法。确定对象是否可迭代的是调用iter(obj)

我找到了一个很好的解决方案这里

isiterable = lambda obj: isinstance(obj, basestring) \or getattr(obj, '__iter__', False)

在Python<=2.5中,你不能也不应该迭代是一个“非正式”的接口。

但是从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——它将在运行时引发一个异常。因此,is实例获得了一个“新”的含义——它只是检查“声明”的类型兼容性,这在Python中是一个很好的方法。

另一方面,如果你的对象不能满足你需要的接口,你该怎么办?举个例子:

from collections import Iterablefrom traceback import print_exc
def check_and_raise(x):if not isinstance(x, Iterable):raise TypeError, "%s is not iterable" % xelse: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基础设施非常有用。

到目前为止我找到的最好的解决方案:

hasattr(obj, '__contains__')

它基本上检查对象是否实现了in运算符。

优势(其他解决方案都没有这三个):

  • 它是一个表达式(作为lambda工作,而不是尝试…除了变体)
  • 它(应该)由所有可迭代对象实现,包括字符串(而不是__iter__
  • 适用于任何Python>=2.5

备注:

  • “请求原谅,而不是许可”的Python哲学在以下情况下不起作用:例如,在列表中,你有可迭代的和不可迭代的,并且你需要根据它的类型不同地对待每个元素(在try上处理可迭代的,在工作之外处理不可迭代的,但它看起来很丑陋和误导)
  • 这个问题的解决方案尝试实际迭代对象(例如[x for x in obj])以检查它是否可迭代可能会导致大型迭代对象的显着性能损失(特别是如果您只需要迭代的前几个元素,例如)并且应该避免

根据Python 2词汇表,可迭代对象是

所有序列类型(例如liststrtuple)和一些非序列类型,例如dictfile以及您使用__iter__()__getitem__()方法定义的任何类的对象。Iterables可以在for循环中以及许多其他需要序列的地方(zip()、map()、…)中使用。当可迭代对象作为参数传递给内置函数iter()时,它会返回该对象的迭代器。

当然,鉴于Python的一般编码风格基于“请求原谅比请求许可更容易”这一事实,一般的期望是使用

try:for i in object_in_question:do_somethingexcept TypeError:do_something_for_non_iterable

但是如果你需要显式检查它,你可以通过hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__")来测试可迭代对象。你需要检查两者,因为str没有__iter__方法(至少在Python 2中没有,在Python 3中它们有),而且因为generator对象没有__getitem__方法。

在我的脚本中,我经常发现定义一个iterable函数很方便。(现在包含Alfe建议的简化):

import collections
def iterable(obj):return isinstance(obj, collections.Iterable):

因此,您可以测试任何对象是否以非常可读的形式可迭代

if iterable(obj):# act on iterableelse:# not iterable

就像你对callable函数所做的那样

编辑:如果您安装了numpy,您可以简单地执行:从numpy import iterable,就像

def iterable(obj):try: iter(obj)except: return Falsereturn True

如果你没有numpy,你可以简单地实现这段代码,或者上面的代码。

最简单的方法,尊重Python的鸭子打字,是捕获错误(Python完全知道它期望从对象成为迭代器):

class A(object):def __getitem__(self, item):return something
class B(object):def __iter__(self):# Return a compliant iterator. Just an examplereturn iter([])
class C(object):def __iter__(self):# Return crapreturn 1
class D(object): pass
def iterable(obj):try:iter(obj)return Trueexcept:return False
assert iterable(A())assert iterable(B())assert iterable(C())assert not iterable(D())

备注

  1. 如果异常类型相同,则区分对象是否不可迭代或已实现错误__iter__是无关紧要的:无论如何,您将无法迭代对象。
  2. 我想我理解你的担忧:如果没有为我的对象定义__call__,如果我也可以依靠鸭子类型来引发AttributeError,那么callable如何作为检查存在,但这不是可迭代检查的情况?

    我不知道答案,但是你可以实现我(和其他用户)给出的函数,或者只是在你的代码中捕获异常(你在那部分的实现将像我写的函数一样——只要确保你将迭代器创建与代码的其余部分隔离开来,这样你就可以捕获异常并将其与另一个TypeError区分开来。

def is_iterable(x):try:0 in xexcept TypeError:return Falseelse: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))   # tupleassert is_iterable([1,2,3])   # listassert is_iterable({1,2,3})   # setassert is_iterable({1:'one', 2:'two', 3:'three'})   # dictionaryassert 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 collectionsimport numpy
assert isinstance("string", collections.Iterable)assert isinstance("string", collections.Sequence)assert numpy.iterable("string")assert iter("string")assert hasattr("string", '__getitem__')

注意:is_iterable()会对bytesbytearray类型的字符串说是。

  • 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"))

O. P.hasattr(x, '__iter__')方法会对Python 3中的字符串说是,对Python 2中的字符串说不(不管是''b''还是u'')。感谢@LuisMasuelli注意到它也会让你对错误__iter__失望。

我想进一步阐明iter__iter____getitem__的相互作用,以及幕后发生的事情。有了这些知识,你将能够理解为什么你能做的最好的是

try:iter(maybe_iterable)print('iteration will probably work')except TypeError:print('not iterable')

我将首先列出事实,然后快速提醒您在python中使用for循环时会发生什么,然后进行讨论以说明事实。

事实

  1. 如果至少满足以下条件之一,则可以通过调用iter(o)从任何对象o中获取迭代器:

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

    b)o有一个__getitem__方法。

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

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

  4. 在最一般的意义上,除了尝试之外,没有办法检查iter返回的迭代器是否正常。

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

  6. __iter__获胜。如果对象o同时实现了__iter____getitem__iter(o)将调用__iter__

  7. 如果您想使自己的对象可迭代,请始终实现__iter__方法。

for循环

为了继续学习,您需要了解在Python中使用for循环时会发生什么。如果您已经知道,请随时跳到下一节。

当您对某个可迭代对象o使用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 StopIterationreturn r

DemoIterable上迭代:

>>> di = DemoIterable()>>> for x in di:...     print(x)...__iter__ called__next__ called9__next__ called8__next__ called10__next__ called3__next__ called10__next__ calledraising StopIteration

讨论和插图

关于第1点和第2点:获得迭代器和不可靠的检查

考虑以下类:

class BasicIterable(object):def __getitem__(self, item):if item == 3:raise IndexErrorreturn 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的流利的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,它是由iter返回的迭代器作为参数提供给__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)...012

这是另一个例子,以便深入了解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)...hiStackOverflow!

在第4点和第5点:#0在调用#1时检查迭代器

当为对象o调用iter(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构造迭代器会成功,但会在第一次调用__next__时抛出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 ExceptionException

第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)...ABC

您的自定义可迭代对象应该实现__iter__的原因如下:

  1. 如果实现__iter__,实例将被视为可迭代的,isinstance(o, collections.abc.Iterable)将返回True
  2. 如果__iter__返回的对象不是迭代器,iter将立即失败并引发TypeError
  3. __getitem__的特殊处理是出于向后兼容的原因。再次引用Fluent Python:

这就是为什么任何Python序列都是可迭代的:它们都实现了__getitem__。事实上,标准序列也实现了__iter__,你的也应该实现,因为__getitem__的特殊处理是出于向后兼容的原因,并且可能是将来会消失(虽然我写这篇文章时不建议使用)。

有一个这样的内置函数:

from pandas.util.testing import isiterable

如果对象是可迭代的,则以下代码中的isiterable func返回True。如果它不是可迭代的,则返回False

def isiterable(object_):return hasattr(type(object_), "__iter__")

示例

fruits = ("apple", "banana", "peach")isiterable(fruits) # returns True
num = 345isiterable(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

python3.5开始,您可以使用标准库中的打字模块来处理与类型相关的事情:

from typing import Iterable
...
if isinstance(my_item, Iterable):print(True)

您可以检查__len__属性,而不是检查__iter__属性,该属性由每个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很有用。)

我一直不明白为什么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对象)

不是真正的“正确”,但可以作为最常见类型的快速检查,如字符串,元组,浮点数等…

>>> '__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)

我最近一直在研究这个问题。基于此,我的结论是,现在这是最好的方法:

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 Falseelse: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)    # OoopsTraceback (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()起作用的情况是在使用Python 2时使用UserDict。如果这相关,可以使用isinstance(item, (Iterable, UserDict))来解决这个问题。

有很多方法可以检查对象是否可迭代:

from collections.abc import Iterablemyobject = 'Roster'  
if isinstance(myobject , Iterable):print(f"{myobject } is iterable")else:print(f"strong text{myobject } is not iterable")

在我的代码中,我用来检查不可迭代的对象:

hasattr(myobject,'__trunc__')

这非常快,也可以用来检查可迭代对象(使用not)。

我不是100%确定这个解决方案是否适用于所有对象,也许其他人可以提供更多背景信息。__trunc__方法与数字类型有关(所有可以四舍五入为整数的对象都需要它)。但是我没有发现任何包含__trunc____iter____getitem__的对象。