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

2024-03-28 09:14:56 发布

您现在位置:Python中文网/ 问答频道 /正文

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

hasattr(myObj, '__iter__')

但我不知道这是多么愚蠢的证据。


Tags: 方法证据hasattritermyobj解决办法isiterable
3条回答

鸭子打字

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

# for obj in iterator:
#     pass

类型检查

使用Abstract Base Classes。它们至少需要Python2.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所述:

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

我想对iter__iter____getitem__之间的相互作用,以及窗帘后面发生的事情,多透露一点。有了这些知识,你就能明白为什么你能做的最好

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

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

事实

  1. 如果以下条件中至少有一个成立,则可以通过调用iter(o)从任何对象o获取迭代器:

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

    b)o有一个__getitem__方法。

  2. 检查IterableSequence的实例,或检查 属性__iter__不够。

  3. 如果对象o只实现__getitem__,而不实现__iter__,则iter(o)将构造 试图通过整数索引从o获取项的迭代器,从索引0开始。迭代器将捕获任何引发的IndexError(但没有其他错误),然后引发StopIteration本身。

  4. 在最一般的意义上,除了尝试之外,没有办法检查由iter返回的迭代器是否正常。

  5. 如果对象o实现__iter__,则iter函数将确保 __iter__返回的对象是迭代器。没有健康检查 如果一个对象只实现__getitem__

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

  7. 如果您想使您自己的对象可iterable,请始终从abstract base classIterable或其子类之一继承。您必须实现__iter__方法,否则将像Sequence那样提供它。

for

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

当对某个iterable对象使用for item in o时,Python将调用iter(o),并期望返回一个迭代器对象。迭代器是实现Python 2中的__next__(或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

讨论和说明

关于第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__属性,并且不被视为IterableSequence的实例:

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

这就是为什么Luciano Ramalho的Fluent Python建议调用iter并处理潜在的TypeError作为检查对象是否可iterable的最精确方法。直接从书中引用:

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.

在第3点:迭代仅提供__getitem__,但不提供__iter__的对象

BasicIterable的实例进行迭代可以按预期工作:Python 构造一个迭代器,该迭代器尝试从零开始按索引获取项,直到引发IndexError。demo对象的__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

注意,迭代器在不能返回下一个i时引发StopIteration以及为item == 3引发的IndexError在内部处理。这就是为什么用^{循环遍历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点:iterable类应该实现__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

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

  1. 如果实现__iter__,实例将被视为可访问的,并且isinstance(o, collections.abc.Iterable)将返回True
  2. 如果__iter__返回的对象不是迭代器,iter将立即失败并引发一个TypeError
  3. 由于向后兼容的原因,存在对__getitem__的特殊处理。再次引用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).

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

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

    内置的iter检查__iter__方法,如果是字符串,则检查__getitem__方法。

  2. 另一种常见的pythonic方法是假设一个iterable,如果它不能在给定的对象上工作,那么它将优雅地失败。Python词汇表:

    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. ^{}模块提供一些抽象基类,这些基类允许询问类或实例是否提供特定功能,例如:

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

    但是,这不会检查可通过__getitem__访问的类。

相关问题 更多 >