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

hasattr(myObj, '__iter__')

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


当前回答

我想多讲一点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__方法。 对象的实例,或者对象的实例 属性__iter__是不够的。 如果对象o只实现__getitem__,而不实现__iter__,则会构造iter(o) 一个迭代器,试图通过整数索引从o中获取项目,从索引0开始。迭代器将捕获所引发的任何IndexError(但没有其他错误),然后引发StopIteration本身。 在最一般的意义上,没有办法检查iter返回的迭代器是否正常,只能尝试它。 如果对象o实现了__iter__,则iter函数将确保 __iter__返回的对象是一个迭代器。没有健康检查 如果一个对象只实现__getitem__。 __iter__获胜。如果对象o同时实现了__iter__和__getitem__,则iter(o)将调用__iter__。 如果你想让你自己的对象可迭代,总是实现__iter__方法。

for循环

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

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

按照惯例,迭代器的__iter__方法应该返回对象本身(即返回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推荐调用iter并处理潜在的TypeError作为检查对象是否可迭代的最准确方法。直接从书中引用:

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

关于第3点:迭代只提供__getitem__而不提供__iter__的对象

在BasicIterable实例上迭代工作如预期:Python 构造一个迭代器,该迭代器尝试按索引获取项目,从0开始,直到引发IndexError。演示对象的__getitem__方法只是返回由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)
...
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
!

第4点和第5点:iter在调用__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

第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__列表。__getitem__(使用方括号表示),将正常工作:

>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
...     print(x)
... 
A
B
C

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

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

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

其他回答

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

Hasattr(obj, '__contains__')

它主要检查对象是否实现了in操作符。

优点(其他解决方案都不具备这三个优点):

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

注:

Python的“请求原谅,而不是允许”的哲学在例如,在一个列表中,你有可迭代对象和不可迭代对象,你需要根据它的类型区别对待每个元素(在try上处理可迭代对象,在except上处理不可迭代对象可以工作,但它看起来很丑,会误导人)时,就不会很好地工作了。 对于这个问题的解决方案,试图实际遍历对象(例如[x for x in obj])来检查它是否为可迭代对象,可能会导致对大型可迭代对象的显著性能损失(特别是如果你只需要可迭代对象的前几个元素,例如),应该避免

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

但是从Python 2.6和3.0开始,你可以利用新的ABC(抽象基类)基础设施以及一些内置的ABC,这些ABC在collections模块中可用:

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将自动识别该类的object为Iterable。

如果你只是期望一个可迭代对象,遍历它,然后忘记它。另一方面,如果您需要根据输入类型执行不同的操作,那么您可能会发现ABC基础结构非常有用。

根据Python 2术语表,可迭代对象是

所有序列类型(如list、str和tuple)和一些非序列类型(如dict和file)以及使用__iter__()或__getitem__()方法定义的任何类的对象。可迭代对象可用于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__")或hasattr(object_in_question, "__getitem__")来测试可迭代对象。你需要检查两者,因为strs没有__iter__方法(至少在Python 2中没有,在Python 3中有),而且生成器对象没有__getitem__方法。

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

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

不要检查你的鸭子是否真的是一只鸭子,看看它是否可迭代,就像它是可迭代的一样对待它,如果不是就抱怨。