首页 文章

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

提问于
浏览
853

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

hasattr(myObj, '__iter__')

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

19 回答

  • 8

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

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

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

    事实

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

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

    b) o 有一个 __getitem__ 方法 .

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

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

    • 从最普遍的意义上讲,没有办法检查 iter 返回的迭代器是否理智,而不是尝试它 .

    • 如果对象 o 实现 __iter__iter 函数将确保 __iter__ 返回的对象是迭代器 . 如果对象仅实现 __getitem__ ,则不进行健全性检查 .

    • __iter__ 胜利 . 如果对象 o 同时实现 __iter____getitem__iter(o) 将调用 __iter__ .

    • 如果要使自己的对象可迭代,请始终实现 __iter__ 方法 .

    for循环

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

    当您对某些可迭代对象 o 使用_88660时,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
    

    讨论和插图

    On point 1 and 2: getting an iterator and unreliable checks

    考虑以下课程:

    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的Fluent Python建议调用 iter 并将潜在的 TypeError 作为检查对象是否可迭代的最准确方法 . 直接从书中引用:

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

    On point 3: Iterating over objects which only provide getitem, but not iter

    迭代 BasicIterable 的实例按预期工作:Python构造一个迭代器,它尝试按索引获取项目,从零开始,直到 IndexError 被引发 . 演示对象的 __getitem__ 方法只返回由 iter 返回的迭代器作为 __getitem__(self, item) 参数提供的 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 . 这就是为什么循环 BasicIterablefor 循环按预期工作的原因:

    >>> 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__ 的调用被委托给 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
    !
    

    On point 4 and 5: iter checks for an iterator when it calls iter

    当为对象 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__ 时抛出异常 .

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

    On point 6: iter wins

    这个很简单 . 如果一个对象实现 __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
    

    On point 7: your iterable classes should implement iter

    您可能会问自己为什么大多数内置序列(如 list )在 __getitem__ 足够时实现 __iter__ 方法 .

    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
    

    您的自定义iterables应该实现 __iter__ 的原因如下:

    • 如果实现 __iter__ ,则实例将被视为可迭代, isinstance(o, collections.Iterable) 将返回 True .

    • 如果 __iter__ 返回的对象不是迭代器, iter 将立即失败并引发 TypeError .

    • 出于向后兼容性原因,存在 __getitem__ 的特殊处理 . 再次从Fluent Python引用:

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

  • 18

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

    hasattr(obj, '__contains__')

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

    Advantages (其他解决方案都没有全部三个):

    • 它是一个表达式(作为 lambda ,而不是 try...except 变体)

    • 它应该(应该)由所有迭代实现,包括 strings (而不是 __iter__

    • 适用于任何Python> = 2.5

    笔记:

    • "ask for forgiveness, not permission"的Python哲学没有't work well when e.g. in a list you have both iterables and non-iterables and you need to treat each element differently according to it'类型(在try和非迭代上处理迭代,除了可行,但它看起来很丑陋和误导)

    • 这个问题的解决方案试图实际迭代对象(例如[x for obj])来检查它是否可迭代可能会对大型迭代产生显着的性能损失(特别是如果你只需要迭代的前几个元素,例如)并且应该避免

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

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

  • 4

    鸭子打字

    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
    

    但是,如by the documentation所述_84620_更可靠:

    检查isinstance(obj,Iterable)检测注册为Iterable或具有__iter __()方法的类,但它不检测使用__getitem __()方法迭代的类 . 确定对象是否可迭代的唯一可靠方法是调用iter(obj) .

  • 0

    pandas有这样的内置函数:

    from pandas.util.testing import isiterable
    
  • 688
    • 检查 __iter__ 适用于序列类型,但它会失败,例如字符串 in Python 2 . 我也想知道正确的答案,在此之前,这里有一种可能性(也适用于字符串):
    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(更容易要求宽恕而不是许可)的编程风格 . ...尝试:
    _ =(e表示my_object中的e)
    除TypeError外:
    打印my_object,'不可迭代'

    • collections模块提供了一些抽象基类,如果它们提供特定功能,它们可以询问类或实例,例如:
    import collections
    
    if isinstance(e, collections.Iterable):
        # e is iterable
    

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

  • 504

    你可以试试这个:

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

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

  • 28

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

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

    如果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
    
  • 4
    def is_iterable(x):
        try:
            0 in x
        except TypeError:
            return False
        else:
            return True
    

    这将对所有可迭代对象都说是,但它将 say no to strings in 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__ .

  • -2

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

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

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

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

  • 15

    在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获得了一个"new"含义 - 它只是检查"declared"类型兼容性,这是一个很好的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基础结构非常有用 .

  • 11

    我总是不知道为什么python有 callable(obj) -> bool 而不是 iterable(obj) -> bool ......
    肯定它更容易做 hasattr(obj,'__call__') 即使它更慢 .

    由于几乎所有其他答案都建议使用 try / except TypeError ,其中对异常的测试通常被认为是任何语言中的不良做法,这里是 iterable(obj) -> bool 的实现,我越来越喜欢并经常使用:

    对于python 2 's sake, I' ll使用lambda只是为了额外的性能提升......
    (在python 3中,用于定义函数的内容并不重要, deflambda 的速度大致相同)

    iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')
    

    请注意,对于具有 __iter__ 的对象,此函数执行速度更快,因为它不会测试 __getitem__ .

    大多数可迭代对象应该依赖于 __iter__ ,其中特殊情况对象回退到 __getitem__ ,尽管对象是可迭代的 .
    (因为这是标准的,它也会影响C对象)

  • 1

    根据Python 2 Glossary,迭代是

    所有序列类型(例如list,str和tuple)和一些非序列类型(如dict和file)以及使用__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__ 方法 .

  • 2

    在我的脚本中,我经常发现定义 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,你可以简单地实现这个代码,或上面的代码 .

  • 16

    尊重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())
    

    Notes

    • 对象的区别是无关紧要的如果异常类型相同,则可迭代,或者已经实现了错误的 __iter__ :无论如何,您将无法迭代该对象 .

    • 我想我理解你的关注:如果 callable 如果没有为我的对象定义 __call__ ,如果我还可以依赖鸭子打字来提升 AttributeError ,那么 callable 是如何存在的,但是这不是可迭代检查的情况?

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

  • 1

    您可以检查 __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
    

    由于显而易见的原因,无法迭代的对象不会实现这一点 . 但是,它不会捕获不实现它的用户定义的iterables,也不会捕获 iter 可以处理的生成器表达式 . 但是,这可以在一行中完成,添加一个简单的 or 表达式检查生成器将解决这个问题 . (注意写 type(my_generator_expression) == generator 会抛出 NameError . 请改为参考this . )

    您可以使用类型中的GeneratorType:>>>导入类型

    types.GeneratorType
    <class'generator'>
    gen =(i for i in range(10))
    isinstance(gen,types.GeneratorType)
    真正
    ---接受了utdemir的回答

    (这使得检查是否可以在对象上调用 len 非常有用 . )

  • 0

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

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

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

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

相关问题