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

有没有类似的方法isiterable?到目前为止,我发现的唯一解决方案是调用

hasattr(myObj, '__iter__')

但是我不确定这有多愚蠢。

Is there a method like isiterable? The only solution I have found so far is to call

hasattr(myObj, '__iter__')

But I am not sure how fool-proof this is.


回答 0

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

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()UserDict使用Python2。如果相关,则可以使用isinstance(item, (Iterable, UserDict))它进行覆盖。

I’ve been studying this problem quite a bit lately. Based on that my conclusion is that nowadays this is the best approach:

from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower

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

The above has been recommended already earlier, but the general consensus has been that using iter() would be better:

def iterable(obj):
    try:
        iter(obj)
    except Exception:
        return False
    else:
        return True

We’ve used iter() in our code as well for this purpose, but I’ve lately started to get more and more annoyed by objects which only have __getitem__ being considered iterable. There are valid reasons to have __getitem__ in a non-iterable object and with them the above code doesn’t work well. As a real life example we can use Faker. The above code reports it being iterable but actually trying to iterate it causes an AttributeError (tested with 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'

If we’d use insinstance(), we wouldn’t accidentally consider Faker instances (or any other objects having only __getitem__) to be iterable:

>>> from collections.abc import Iterable
>>> from faker import Faker
>>> isinstance(Faker(), Iterable)
False

Earlier answers commented that using iter() is safer as the old way to implement iteration in Python was based on __getitem__ and the isinstance() approach wouldn’t detect that. This may have been true with old Python versions, but based on my pretty exhaustive testing isinstance() works great nowadays. The only case where isinstance() didn’t work but iter() did was with UserDict when using Python 2. If that’s relevant, it’s possible to use isinstance(item, (Iterable, UserDict)) to get that covered.


回答 1

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

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

    所述iter内置的检查的__iter__方法或串的情况下的__getitem__方法。

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

    通过检查对象的方法或属性签名而不是通过与某种类型对象的显式关系来确定对象类型的Python编程风格(“如果它看起来像鸭子,并且像鸭子一样嘎嘎叫,那一定是鸭子。”)通过强调接口经过精心设计的代码(而不是特定类型)通过允许多态替换来提高其灵活性。鸭式输入避免使用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. Checking for __iter__ works on sequence types, but it would fail on e.g. strings in Python 2. I would like to know the right answer too, until then, here is one possibility (which would work on strings, too):

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

    The iter built-in checks for the __iter__ method or in the case of strings the __getitem__ method.

  2. Another general pythonic approach is to assume an iterable, then fail gracefully if it does not work on the given object. The Python glossary:

    Pythonic programming style that determines an object’s type by inspection of its method or attribute signature rather than by explicit relationship to some type object (“If it looks like a duck and quacks like a duck, it must be a duck.”) By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using type() or isinstance(). Instead, it typically employs the EAFP (Easier to Ask Forgiveness than Permission) style of programming.

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
    
  3. The collections module provides some abstract base classes, which allow to ask classes or instances if they provide particular functionality, for example:

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

    However, this does not check for classes that are iterable through __getitem__.


回答 2

鸭打字

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

# for obj in iterator:
#     pass

类型检查

使用抽象基类。他们至少需要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()文档所描述那样更可靠:

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

Duck typing

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

# for obj in iterator:
#     pass

Type checking

Use the Abstract Base Classes. They need at least Python 2.6 and work only for new-style classes.

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

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

However, iter() is a bit more reliable as described by the documentation:

Checking isinstance(obj, Iterable) detects classes that are registered as Iterable or that have an __iter__() method, but it does not detect classes that iterate with the __getitem__() method. The only reliable way to determine whether an object is iterable is to call iter(obj).


回答 3

我想摆脱一点点的相互作用越轻iter__iter____getitem__会发生什么窗帘后面。有了这些知识,您将能够理解为什么您能做到最好的是

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

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

事实

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

    a)o具有__iter__返回迭代器对象的方法。迭代器是任何具有__iter__和方法__next__(Python 2 :)的对象next

    b)o__getitem__方法。

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

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

  4. 从最一般的意义上讲,iter除了尝试一下之外,没有其他方法可以检查返回的迭代器是否正常。

  5. 如果o实现了对象__iter__,则该iter函数将确保返回的对象__iter__是迭代器。如果对象仅实现,则没有健全性检查__getitem__

  6. __iter__胜。如果一个对象同时o实现__iter__and __getitem__iter(o)将调用__iter__

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

for 循环

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

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

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

调用iter的实例BasicIterable将返回迭代器,而不会出现任何问题,因为BasicIterableImplements __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的Fluent Python建议调用iter和处理潜能TypeError作为检查对象是否可迭代的最准确方法。直接从书中引用:

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

关于第3点:遍历仅提供__getitem__,但不提供的对象__iter__

BasicIterable按预期的方式对一个工作实例进行迭代:Python构造了一个迭代器,该迭代器尝试从索引开始(从零开始)获取项目,直到IndexError引发。演示对象的__getitem__方法仅返回item__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

注意,将to __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__

iter(o)为对象调用时oiter将确保方法的返回值(__iter__如果存在)是迭代器。这意味着返回的对象必须实现__next__(或next在Python 2中)和__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,但会在第一次调用时引发Exception __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__and __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)
... 
A
B
C

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

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

这就是任何Python序列都是可迭代的原因:它们都实现了__getitem__。实际上,标准序列也可以实现__iter__,您也应该实现,因为__getitem__出于向后兼容的原因而存在对的特殊处理,并且可能在将来消失(尽管在我撰写本文时不推荐使用)。

I’d like to shed a little bit more light on the interplay of iter, __iter__ and __getitem__ and what happens behind the curtains. Armed with that knowledge, you will be able to understand why the best you can do is

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

I will list the facts first and then follow up with a quick reminder of what happens when you employ a for loop in python, followed by a discussion to illustrate the facts.

Facts

  1. You can get an iterator from any object o by calling iter(o) if at least one of the following conditions holds true:

    a) o has an __iter__ method which returns an iterator object. An iterator is any object with an __iter__ and a __next__ (Python 2: next) method.

    b) o has a __getitem__ method.

  2. Checking for an instance of Iterable or Sequence, or checking for the attribute __iter__ is not enough.

  3. If an object o implements only __getitem__, but not __iter__, iter(o) will construct an iterator that tries to fetch items from o by integer index, starting at index 0. The iterator will catch any IndexError (but no other errors) that is raised and then raises StopIteration itself.

  4. In the most general sense, there’s no way to check whether the iterator returned by iter is sane other than to try it out.

  5. If an object o implements __iter__, the iter function will make sure that the object returned by __iter__ is an iterator. There is no sanity check if an object only implements __getitem__.

  6. __iter__ wins. If an object o implements both __iter__ and __getitem__, iter(o) will call __iter__.

  7. If you want to make your own objects iterable, always implement the __iter__ method.

for loops

In order to follow along, you need an understanding of what happens when you employ a for loop in Python. Feel free to skip right to the next section if you already know.

When you use for item in o for some iterable object o, Python calls iter(o) and expects an iterator object as the return value. An iterator is any object which implements a __next__ (or next in Python 2) method and an __iter__ method.

By convention, the __iter__ method of an iterator should return the object itself (i.e. return self). Python then calls next on the iterator until StopIteration is raised. All of this happens implicitly, but the following demonstration makes it visible:

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

Iteration over a 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

Discussion and illustrations

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

Consider the following class:

class BasicIterable(object):
    def __getitem__(self, item):
        if item == 3:
            raise IndexError
        return item

Calling iter with an instance of BasicIterable will return an iterator without any problems because BasicIterable implements __getitem__.

>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>

However, it is important to note that b does not have the __iter__ attribute and is not considered an instance of Iterable or Sequence:

>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False

This is why Fluent Python by Luciano Ramalho recommends calling iter and handling the potential TypeError as the most accurate way to check whether an object is iterable. Quoting directly from the book:

As of Python 3.4, the most accurate way to check whether an object x is iterable is to call iter(x) and handle a TypeError exception if it isn’t. This is more accurate than using isinstance(x, abc.Iterable) , because iter(x) also considers the legacy __getitem__ method, while the Iterable ABC does not.

On point 3: Iterating over objects which only provide __getitem__, but not __iter__

Iterating over an instance of BasicIterable works as expected: Python constructs an iterator that tries to fetch items by index, starting at zero, until an IndexError is raised. The demo object’s __getitem__ method simply returns the item which was supplied as the argument to __getitem__(self, item) by the iterator returned by 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

Note that the iterator raises StopIteration when it cannot return the next item and that the IndexError which is raised for item == 3 is handled internally. This is why looping over a BasicIterable with a for loop works as expected:

>>> for x in b:
...     print(x)
...
0
1
2

Here’s another example in order to drive home the concept of how the iterator returned by iter tries to access items by index. WrappedDict does not inherit from dict, which means instances won’t have an __iter__ method.

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

Note that calls to __getitem__ are delegated to dict.__getitem__ for which the square bracket notation is simply a shorthand.

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

When iter(o) is called for an object o, iter will make sure that the return value of __iter__, if the method is present, is an iterator. This means that the returned object must implement __next__ (or next in Python 2) and __iter__. iter cannot perform any sanity checks for objects which only provide __getitem__, because it has no way to check whether the items of the object are accessible by integer index.

class FailIterIterable(object):
    def __iter__(self):
        return object() # not an iterator

class FailGetitemIterable(object):
    def __getitem__(self, item):
        raise Exception

Note that constructing an iterator from FailIterIterable instances fails immediately, while constructing an iterator from FailGetItemIterable succeeds, but will throw an Exception on the first call to __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

This one is straightforward. If an object implements __iter__ and __getitem__, iter will call __iter__. Consider the following class

class IterWinsDemo(object):
    def __iter__(self):
        return iter(['__iter__', 'wins'])

    def __getitem__(self, item):
        return ['__getitem__', 'wins'][item]

and the output when looping over an instance:

>>> iwd = IterWinsDemo()
>>> for x in iwd:
...     print(x)
...
__iter__
wins

On point 7: your iterable classes should implement __iter__

You might ask yourself why most builtin sequences like list implement an __iter__ method when __getitem__ would be sufficient.

class WrappedList(object): # note: no inheritance from list!
    def __init__(self, lst):
        self._list = lst

    def __getitem__(self, item):
        return self._list[item]

After all, iteration over instances of the class above, which delegates calls to __getitem__ to list.__getitem__ (using the square bracket notation), will work fine:

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

The reasons your custom iterables should implement __iter__ are as follows:

  1. If you implement __iter__, instances will be considered iterables, and isinstance(o, collections.abc.Iterable) will return True.
  2. If the the object returned by __iter__ is not an iterator, iter will fail immediately and raise a TypeError.
  3. The special handling of __getitem__ exists for backwards compatibility reasons. Quoting again from Fluent Python:

That is why any Python sequence is iterable: they all implement __getitem__ . In fact, the standard sequences also implement __iter__, and yours should too, because the special handling of __getitem__ exists for backward compatibility reasons and may be gone in the future (although it is not deprecated as I write this).


回答 4

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

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

This isn’t sufficient: the object returned by __iter__ must implement the iteration protocol (i.e. next method). See the relevant section in the documentation.

In Python, a good practice is to “try and see” instead of “checking”.


回答 5

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

但是从Python 2.6和3.0开始,您可以利用新的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会自动将该类的对象识别为Iterable。

因此,如果您只是期望一个可迭代的对象,请对其进行迭代并忘记它。另一方面,如果您需要根据输入类型执行不同的操作,则可能会发现ABC基础结构非常有用。

In Python <= 2.5, you can’t and shouldn’t – iterable was an “informal” interface.

But since Python 2.6 and 3.0 you can leverage the new ABC (abstract base class) infrastructure along with some builtin ABCs which are available in the collections module:

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)

Now, whether this is desirable or actually works, is just a matter of conventions. As you can see, you can register a non-iterable object as Iterable – and it will raise an exception at runtime. Hence, isinstance acquires a “new” meaning – it just checks for “declared” type compatibility, which is a good way to go in Python.

On the other hand, if your object does not satisfy the interface you need, what are you going to do? Take the following example:

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

If the object doesn’t satisfy what you expect, you just throw a TypeError, but if the proper ABC has been registered, your check is unuseful. On the contrary, if the __iter__ method is available Python will automatically recognize object of that class as being Iterable.

So, if you just expect an iterable, iterate over it and forget it. On the other hand, if you need to do different things depending on input type, you might find the ABC infrastructure pretty useful.


回答 6

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

不要运行检查以查看您的鸭子是否真的是鸭子,以查看它是否可迭代,请像对待鸭子一样对待它,否则请抱怨。

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

Don’t run checks to see if your duck really is a duck to see if it is iterable or not, treat it as if it was and complain if it wasn’t.


回答 7

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

from typing import Iterable

...

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

Since Python 3.5 you can use the typing module from the standard library for type related things:

from typing import Iterable

...

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

回答 8

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

hasattr(obj, '__contains__')

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

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

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

笔记:

  • 例如,在列表中同时具有可迭代和不可迭代,并且您需要根据其类型对每个元素进行不同处理(在try和non-上处理可迭代)时,Python的“请求宽恕,而不是允许”的哲学无法很好地工作。上的iterables 起作用,但是看起来很难看并且具有误导性)
  • 尝试实际迭代对象(例如[x for obj中的x的x])以检查其是否可迭代的问题的解决方案,可能会导致大型可迭代对象的性能下降(尤其是如果您只需要可迭代对象的前几个元素,则为示例),应避免

The best solution I’ve found so far:

hasattr(obj, '__contains__')

which basically checks if the object implements the in operator.

Advantages (none of the other solutions has all three):

  • it is an expression (works as a lambda, as opposed to the try…except variant)
  • it is (should be) implemented by all iterables, including strings (as opposed to __iter__)
  • works on any Python >= 2.5

Notes:

  • the Python philosophy of “ask for forgiveness, not permission” doesn’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’s type (treating iterables on try and non-iterables on except would work, but it would look butt-ugly and misleading)
  • solutions to this problem which attempt to actually iterate over the object (e.g. [x for x in obj]) to check if it’s iterable may induce significant performance penalties for large iterables (especially if you just need the first few elements of the iterable, for example) and should be avoided

回答 9

您可以尝试以下方法:

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

如果我们可以使生成器在其上进行迭代(但不要使用生成器,这样就不会占用空间),那么它是可迭代的。好像是“ duh”之类的东西。为什么首先需要确定变量是否可迭代?

You could try this:

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

If we can make a generator that iterates over it (but never use the generator so it doesn’t take up space), it’s iterable. Seems like a “duh” kind of thing. Why do you need to determine if a variable is iterable in the first place?


回答 10

我在这里找到了一个不错的解决方案:

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

I found a nice solution here:

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

回答 11

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

所有序列类型(如liststr,和tuple)和一些非序列类型,如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__")。您需要检查两者,因为strs没有__iter__方法(至少在Python 2中没有),并且generator对象没有__getitem__方法。

According to the Python 2 Glossary, iterables are

all sequence types (such as list, str, and tuple) and some non-sequence types like dict and file and objects of any classes you define with an __iter__() or __getitem__() method. Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), …). When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object.

Of course, given the general coding style for Python based on the fact that it’s “Easier to ask for forgiveness than permission.”, the general expectation is to use

try:
    for i in object_in_question:
        do_something
except TypeError:
    do_something_for_non_iterable

But if you need to check it explicitly, you can test for an iterable by hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). You need to check for both, because strs don’t have an __iter__ method (at least not in Python 2, in Python 3 they do) and because generator objects don’t have a __getitem__ method.


回答 12

我经常在脚本内找到定义iterable函数的方便方法。(现在结合了Alfe建议的简化):

import collections

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

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

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

就像您使用该callable功能一样

编辑:如果您已安装numpy,则可以执行以下操作:from numpy import iterable,这就像

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

如果您没有numpy,则可以简单地实现此代码或上面的代码。

I often find convenient, inside my scripts, to define an iterable function. (Now incorporates Alfe’s suggested simplification):

import collections

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

so you can test if any object is iterable in the very readable form

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

as you would do with thecallable function

EDIT: if you have numpy installed, you can simply do: from numpy import iterable, which is simply something like

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

If you do not have numpy, you can simply implement this code, or the one above.


回答 13

具有这样的内置功能:

from pandas.util.testing import isiterable

has a built-in function like that:

from pandas.util.testing import isiterable

回答 14

它总是躲避我,为什么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对象)

It’s always eluded me as to why python has callable(obj) -> bool but not iterable(obj) -> bool
surely it’s easier to do hasattr(obj,'__call__') even if it is slower.

Since just about every other answer recommends using try/except TypeError, where testing for exceptions is generally considered bad practice among any language, here’s an implementation of iterable(obj) -> bool I’ve grown more fond of and use often:

For python 2’s sake, I’ll use a lambda just for that extra performance boost…
(in python 3 it doesn’t matter what you use for defining the function, def has roughly the same speed as lambda)

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

Note that this function executes faster for objects with __iter__ since it doesn’t test for __getitem__.

Most iterable objects should rely on __iter__ where special-case objects fall back to __getitem__, though either is required for an object to be iterable.
(and since this is standard, it affects C objects as well)


回答 15

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

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

该任择议定书hasattr(x, '__iter__')的做法将是说在Python 3,没有在Python 2串(也罢''b''u'')。感谢@LuisMasuelli注意到它也会让您失望__iter__

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

This will say yes to all manner of iterable objects, but it will say no to strings in Python 2. (That’s what I want for example when a recursive function could take a string or a container of strings. In that situation, asking forgiveness may lead to obfuscode, and it’s better to ask permission first.)

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)

Many other strategies here will say yes to strings. Use them if that’s what you want.

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

Note: is_iterable() will say yes to strings of type bytes and bytearray.

  • bytes objects in Python 3 are iterable True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) There is no such type in Python 2.
  • bytearray objects in Python 2 and 3 are iterable True == is_iterable(bytearray(b"abc"))

The O.P. hasattr(x, '__iter__') approach will say yes to strings in Python 3 and no in Python 2 (no matter whether '' or b'' or u''). Thanks to @LuisMasuelli for noticing it will also let you down on a buggy __iter__.


回答 16

尊重Python的鸭子类型,最简单的方法是捕获错误(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如果我还可以依赖于鸭子类型来引发未为我的对象定义的AttributeErrorif 的检查,那么它如何存在__call__,但可迭代检查不是这种情况?

    我不知道答案,但是您可以实现我(和其他用户)提供的功能,也可以仅捕获代码中的异常(您在该部分中的实现将类似于我编写的功能-只要确保隔离从代码的其余部分创建迭代器,这样您可以捕获异常并将其与另一个区别TypeError

The easiest way, respecting the Python’s duck typing, is to catch the error (Python knows perfectly what does it expect from an object to become an iterator):

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:

  1. It is irrelevant the distinction whether the object is not iterable, or a buggy __iter__ has been implemented, if the exception type is the same: anyway you will not be able to iterate the object.
  2. I think I understand your concern: How does callable exists as a check if I could also rely on duck typing to raise an AttributeError if __call__ is not defined for my object, but that’s not the case for iterable checking?

    I don’t know the answer, but you can either implement the function I (and other users) gave, or just catch the exception in your code (your implementation in that part will be like the function I wrote – just ensure you isolate the iterator creation from the rest of the code so you can capture the exception and distinguish it from another TypeError.


回答 17

如果对象是可迭代的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

The isiterable func at the following code returns True if object is iterable. if it’s not iterable returns False

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

example

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

除了检查__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。请改为参考答案。)

您可以从以下类型使用GeneratorType:

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

— utdemir接受的答案

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

Instead of checking for the __iter__ attribute, you could check for the __len__ attribute, which is implemented by every python builtin iterable, including strings.

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

None-iterable objects would not implement this for obvious reasons. However, it does not catch user-defined iterables that do not implement it, nor do generator expressions, which iter can deal with. However, this can be done in a line, and adding a simple or expression checking for generators would fix this problem. (Note that writing type(my_generator_expression) == generator would throw a NameError. Refer to this answer instead.)

You can use GeneratorType from types:

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

— accepted answer by utdemir

(This makes it useful for checking if you can call len on the object though.)


回答 19

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

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

Not really “correct” but can serve as quick check of most common types like strings, tuples, floats, etc…

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

回答 20

Kinda参加聚会很晚,但是我问了自己这个问题,然后看到了这个答案。我不知道是否有人已经发布了这个。但从本质上讲,我注意到所有可迭代类型的字典中都具有__getitem __()。这是您无需尝试即可检查对象是否可迭代的方式。(双关语意)

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

Kinda late to the party but I asked myself this question and saw this then thought of an answer. I don’t know if someone already posted this. But essentially, I’ve noticed that all iterable types have __getitem__() in their dict. This is how you would check if an object was an iterable without even trying. (Pun intended)

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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。