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

hasattr(myObj, '__iter__')

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


当前回答

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

from typing import Iterable

...

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

其他回答

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

>>> '__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
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

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

我想多讲一点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__存在特殊的处理 将来会消失(尽管在我写这篇文章时它并没有被弃用)。

def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        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))   # 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()会对bytes和bytearray类型的字符串说yes。

Python 3中的bytes对象是可迭代的True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) Python 2和3中的bytearray对象是可迭代的True == is_iterable(bytearray(b"abc"))

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

考虑到Python的duck类型,最简单的方法是捕捉错误(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())

注:

如果异常类型相同,则区分对象是否不可迭代或已经实现了有bug的__iter__是无关紧要的:无论如何,您将无法迭代对象。 我想我理解你的担忧:如果我也可以依赖鸭类型来引发AttributeError,如果__call__没有为我的对象定义,那么callable如何作为检查存在,但这不是可迭代检查的情况? 我不知道答案,但你可以实现我(和其他用户)给出的函数,或者只是在你的代码中捕获异常(你在那部分的实现将像我写的函数一样——只要确保你将迭代器的创建与其余代码隔离开来,这样你就可以捕获异常并将其与另一个TypeError区分开来。