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

时间:2009-12-23 12:13:56

标签: python iterable

是否有类似isiterable的方法?到目前为止我找到的唯一解决方案是打电话

hasattr(myObj, '__iter__')

但我不确定这是多么万无一失。

22 个答案:

答案 0 :(得分:749)

  1. 检查__iter__是否适用于序列类型,但它会失败,例如Python 2中的字符串 。我也想知道正确的答案,在此之前,这是一种可能性(也适用于字符串):

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

    iter方法进行__iter__内置检查,对__getitem__方法进行字符串检查。

  2. 另一种通用的pythonic方法是假设一个可迭代的,如果它不能在给定的对象上工作则优雅地失败。 Python词汇表:

      

    Pythonic编程风格,通过检查对象的方法或属性签名而不是通过与某个类型对象的显式关系来确定对象的类型(“如果它看起来像 duck 而嘎嘎叫像鸭子,它必须是 duck 。“)通过强调接口而不是特定类型,精心设计的代码通过允许多态替换来提高其灵活性。 Duck-typing避免使用type()或isinstance()进行测试。 相反,它通常使用EAFP(更容易请求宽恕而不是权限)的编程风格。

         

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
    
  3. collections模块提供了一些抽象基类,它们可以询问类或实例是否提供特定功能,例如:

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

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

答案 1 :(得分:530)

鸭子打字

try:
    iterator = iter(theElement)
except TypeError:
    # not iterable
else:
    # iterable

# for obj in iterator:
#     pass

类型检查

使用Abstract Base Classes。他们至少需要Python 2.6并且只适用于新式类。

from collections.abc import Iterable   # import directly from collections for Python < 3.3

if isinstance(theElement, Iterable):
    # iterable
else:
    # not iterable

但是iter()更加可靠,如by the documentation所述:

  

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

答案 2 :(得分:86)

我想更多地了解iter__iter____getitem__之间的相互作用以及幕后发生的事情。有了这些知识,你就能理解为什么你能做的最好是

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

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

事实

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

    a){{1有一个o方法,它返回一个迭代器对象。迭代器是具有__iter____iter__(Python 2:__next__)方法的任何对象。

    b)next采用o方法。

  2. 检查__getitem__Iterable的实例,或检查。{ 属性Sequence是不够的。

  3. 如果对象__iter__仅实现o,而不实现__getitem__,则__iter__将构建 一个迭代器,它尝试从整数索引中取iter(o)项,从索引0开始。迭代器将捕获所有引发的o(但没有其他错误),然后引发IndexError本身

  4. 从最普遍的意义上讲,除了试用之外,没有办法检查StopIteration返回的迭代器是否合理。

  5. 如果对象iter实现o__iter__函数将确保 iter返回的对象是迭代器。没有健全检查 如果对象仅实现__iter__

  6. __getitem__获胜。如果对象__iter__同时实现o__iter__,则__getitem__会调用iter(o)

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

  8. __iter__循环

    为了跟进,您需要了解在Python中使用for循环时会发生什么。如果您已经知道,请随意跳到下一部分。

    当您对某个可迭代对象for使用for item in o时,Python会调用o并期望迭代器对象作为返回值。迭代器是实现iter(o)(或Python 2中的__next__)方法和next方法的任何对象。

    按照惯例,迭代器的__iter__方法应该返回对象本身(即__iter__)。 Python然后在迭代器上调用return self,直到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

    讨论和插图

    在第1点和第2点:获取迭代器和不可靠的检查

    考虑以下课程:

    >>> 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
    

    使用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

    这就是Luciano Ramalho Fluent Python建议调用>>> from collections import Iterable, Sequence >>> hasattr(b, '__iter__') False >>> isinstance(b, Iterable) False >>> isinstance(b, Sequence) False 并将潜在iter作为检查对象是否可迭代的最准确方法的原因。直接从书中引用:

      

    从Python 3.4开始,检查对象TypeError是否可迭代的最准确方法是调用x并处理iter(x)异常(如果不是)。这比使用TypeError更准确,因为isinstance(x, abc.Iterable)也会考虑遗留iter(x)方法,而__getitem__ ABC则不会。

    第3点:迭代仅提供Iterable但不提供__getitem__

    的对象

    迭代__iter__的实例按预期工作:Python 构造一个迭代器,它尝试按索引获取项目,从零开始,直到引发BasicIterable。演示对象的IndexError方法只返回由__getitem__返回的迭代器作为参数提供给item的{​​{1}}。

    __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按预期工作的原因:

    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__

    在第4点和第5点: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 ! 时检查迭代器:

    当为对象iter调用__iter__时,iter(o)将确保o的返回值(如果存在方法)是迭代器。这意味着返回的对象 必须实现iter(或Python 2中的__iter__)和__next__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

    第6点:__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 __iter____iter__将调用__getitem__。考虑以下课程

    iter

    和循环实例时的输出:

    __iter__

    第7点:您的可迭代类应实现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 }会在__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__

    自定义iterables应实现list.__getitem__的原因如下:

    1. 如果您实施>>> wl = WrappedList(['A', 'B', 'C']) >>> for x in wl: ... print(x) ... A B C ,则实例将被视为可迭代,__iter__将返回__iter__
    2. 如果isinstance(o, collections.Iterable)返回的对象不是迭代器,True将立即失败并引发__iter__
    3. 出于向后兼容性原因,存在iter的特殊处理。再次引用Fluent Python:
    4.   

      这就是为什么任何Python序列都是可迭代的:它们都实现了TypeError。事实上,   标准序列也实现__getitem__,你的也应该实现,因为   __getitem__的特殊处理是出于向后兼容性原因而存在的   在将来消失了(尽管我写这篇文章并没有被弃用)。

答案 3 :(得分:79)

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

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()起作用的唯一情况是使用Python 2时使用了UserDict。如果相关,则可以使用isinstance(item, (Iterable, UserDict))进行覆盖。

答案 4 :(得分:31)

这还不够:__iter__返回的对象必须实现迭代协议(即next方法)。请参阅documentation

中的相关部分

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

答案 5 :(得分:20)

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

不要运行检查以查看,如果你的鸭子确实是一只鸭子,看它是否可以迭代,就把它当作可能,如果不是就抱怨。

答案 6 :(得分:19)

在Python&lt; = 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基础结构非常有用。

答案 7 :(得分:17)

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

hasattr(obj, '__contains__')

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

优势(其他解决方案都没有全部三种):

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

注意:

  • Python的“请求宽恕而不是许可”的哲学在例如在列表中你有迭代和非迭代,你需要根据它的类型区别对待每个元素(处理try和非迭代上的迭代,除了工作,但它看起来像是 - 丑陋和误导)
  • 试图实际迭代对象的这个问题的解决方案(例如[x for obj])检查它是否可迭代可能会导致大型迭代的显着性能损失(特别是如果你只需要前几个元素)例如,可迭代,应该避免

答案 8 :(得分:13)

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

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

答案 9 :(得分:12)

你可以试试这个:

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

如果我们可以生成一个迭代它的生成器(但从不使用生成器,因此它不占用空间),它是可迭代的。看起来像是一种“呃”的东西。为什么你需要首先确定一个变量是否可迭代?

答案 10 :(得分:10)

根据Python 2 Glossary,迭代是

  

所有序列类型(例如liststrtuple)和一些非序列类型,例如dictfile以及任何对象您使用__iter__()__getitem__()方法定义的类。 Iterables可用于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没有__iter__方法(至少在Python 2中没有,在Python 3中没有),并且因为generator对象没有有一个__getitem__方法。

答案 11 :(得分:7)

由于 Python 3.5 ,您可以使用标准库中的typing模块来处理类型相关的事情:

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)

答案 12 :(得分:5)

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

import collections

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

所以你可以用非常易读的形式测试任何对象是否可以迭代

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,你可以简单地实现这个代码或上面的代码。

答案 13 :(得分:5)

有一个内置函数:

from pandas.util.testing import isiterable

答案 14 :(得分:3)

def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

这会对所有可迭代对象都说“是”,但它会对Python 2中的字符串说“不”。 (这就是我想要的例子,当递归函数可以接受字符串或字符串容器时。在这种情况下,asking forgiveness可能会导致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)

这里的许多其他策略都会对字符串说“是”。如果这就是你想要的,请使用它们。

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()会对bytesbytearray类型的字符串说“是”。

    Python 3中的
  • bytes个对象是可迭代的True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) Python 2中没有这样的类型。
  • Python 2和3中的
  • bytearray个对象是可迭代的True == is_iterable(bytearray(b"abc"))

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

答案 15 :(得分:2)

尊重Python duck typing的最简单方法是捕获错误(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. 如果异常类型相同,那么对象是不可迭代的,还是已经实现了错误的__iter__,这是无关紧要的:无论如何,你将无法迭代该对象。
  2. 我想我理解您的担忧:callable如何作为支票存在,如果AttributeError如果__call__未定义,我是否也可以依赖鸭子打字来提出TypeError我的对象,但是迭代检查的情况不是这样吗?

    我不知道答案,但是您可以实现我(和其他用户)给出的函数,或者只是在代码中捕获异常(您在该部分中的实现将类似于我编写的函数 - 只需确保将迭代器创建与其余代码隔离开来,这样您就可以捕获异常并将其与其他=IF([NoData]Sheet1!C4=ISBLANK(TRUE)," ",[NoData]Sheet1!A4) =IF([NoData]Sheet1!C4=ISBLANK(TRUE)," ",[NoData]Sheet1!C4) =IF([NoData]Sheet1!C4=ISBLANK(TRUE)," ",[NoData]Sheet1!K4) 区分开来。

答案 16 :(得分:2)

关于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 :(得分:1)

如果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

答案 18 :(得分:1)

您可以检查__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。请参考this的答案。)

  

您可以通过以下类型使用GeneratorType:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True
     

--- utdemir接受的答案

(这对于检查您是否可以在对象上调用len很有用。)

答案 19 :(得分:1)

金达晚了晚会,但我问了自己这个问题,然后想到了答案。我不知道是否有人已经发布了这个。但从本质上讲,我注意到所有可迭代类型的字典中都包含“ getitem ”。这是您无需尝试即可检查对象是否可迭代的方式。 (双关语意)

def is_attr(arg):
    return '__getitem__' in dir(arg)

答案 20 :(得分:0)

不是真的“正确” ,但是可以用作大多数常见类型的快速检查,例如字符串,元组,浮点数等等。

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

答案 21 :(得分:-2)

除了常规尝试和除外,你可以运行帮助。

temp= [1,2,3,4]
help(temp)

help将提供可以在该对象上运行的所有方法(它可以是任何对象,并且可能不是示例中的列表),在这种情况下是临时的。

注意:这将是您手动执行的操作。