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

有没有像isiterable?我目前找到的唯一解决方案就是打电话

hasattr(myObj, '__iter__')

但我不确定这是多么的愚蠢。


  1. 检查__iter__序列类型的工作,但是它会在例如Python 2中的字符串上失败。我想知道正确的答案,在那之前,这里有一种可能性(也可以在字符串上工作):
    try:
        some_object_iterator = iter(some_object)
    except TypeError as te:
        print some_object, 'is not iterable'

    该方法的iter内置检查__iter__或字符串__getitem__方法的内置检查。

  2. 另一种普遍的pythonic方法是假设一个可迭代的,然后优雅地失败,如果它不能在给定的对象上工作。Python词汇表:

    Pythonic编程风格通过检查它的方法或属性签名而不是通过与某些类型对象的显式关系来确定对象的类型(“如果它看起来像鸭子鸭子一样,它肯定是鸭子。”)通过强调接口而不是特定类型,精心设计的代码通过允许多态替代来提高其灵活性。鸭子键入避免使用type()或isinstance()进行测试。相反,它通常采用EAFP(容易提出宽恕而不是权限)编程风格。

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. collections模块提供了一些抽象基类,它允许询问类或实例是否提供特定功能,例如:
    import collections
    
    if isinstance(e, collections.Iterable):
        # e is iterable

    但是,这并不检查可迭代的类__getitem__


鸭子打字

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

# for obj in iterator:
#     pass

类型检查

使用抽象基类。他们至少需要Python 2.6,并且仅适用于新式类。

import collections

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

但是,iter()文档所述,它更可靠一些:

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


我想阐明的相互多一点光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. 检查Iterableor 的实例Sequence或检查属性__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. 如果您想让自己的对象可迭代,请始终实施该__iter__方法。

for 循环

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

当你使用for item in o某个可迭代对象时o,Python会调用iter(o)并期待一个迭代器对象作为返回值。迭代器是实现__next__(或next在Python 2中)方法和__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

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

请注意,调用__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____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.Iterable)会返回True
  2. 如果返回的对象__iter__不是迭代器,iter将立即失败并引发一个TypeError
  3. __getitem__出于向后兼容性的原因存在特殊处理。从Fluent Python再次引用:

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

添加评论

友情链接:蝴蝶教程