"iterable" 在 Python 中到底是什么意思?为什么我实现了“__getitem__()”的对象不是可迭代对象?

What exactly does "iterable" mean in Python? Why isn't my object which implements `__getitem__()` an iterable?

首先我想澄清一下,我不是在问什么是 "iterator"。

这就是术语 "iterable" 在 Python 的 doc 中的定义:

iterable

An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, 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. This iterator is good for one pass over the set of values. When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself. The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop.

See also iterator, sequence, and generator.

other people suggested一样,使用isinstance(e, collections.Iterable)是检查对象是否可迭代的最pythonic方式。
所以我用 Python 3.4.3:

做了一些测试
from collections.abc import Iterable

class MyTrain:
    def __getitem__(self, index):
        if index > 3:
            raise IndexError("that's enough!")

        return index

for name in MyTrain():
    print(name)  # 0, 1, 2, 3

print(isinstance(MyTrain(), Iterable))  # False

结果很奇怪:MyTrain定义了__getitem__方法,但它不被认为是可迭代对象,更何况它能够一次返回一个数字。

然后我删除了 __getitem__ 并添加了 __iter__ 方法:

from collections.abc import Iterable

class MyTrain:    
    def __iter__(self):
        print("__iter__ called")
        pass

print(isinstance(MyTrain(), Iterable))  # True

for name in MyTrain():
    print(name)  # TypeError: iter() returned non-iterator of type 'NoneType'

它现在被认为是一个 "true" 可迭代对象,尽管它在迭代时不能产生任何东西。

我是不是误解了什么或者文档不正确?

一个可迭代对象。但是,您没有继承自 abc.Iterable,因此 Python 自然不会将其报告为 class 的后代。这两件事 - 作为一个可迭代的,并且从那个基础 class 下降 - 是完全分开的。

Iterable 是允许对其元素进行某种迭代的东西(集合任何东西)。但是 python 中的通用迭代方式是什么?即使用-in关键字,即使用对象的__iter__方法。因此,在这个术语中,任何定义 __iter__ 的对象都可以与 in 一起使用,并且是一个 Iterable。

所以,大多数 'duck-typish' 检查对象是否可迭代的方法是对象是否是这个,(是的,我隐含地知道这也是 isinstance 情况下发生的事情,由于虚拟 类)

hasattr(train, '__iter__')

因为根据鸭子类型,我们关心对象提供的行为而不是它的祖先。

如果您的 __iter__ 实现有误,这并不意味着对象不可迭代,它只是意味着您的代码中存在错误。

注意:- 那些没有定义 __iter__ 的对象在一般意义上仍然可以迭代,通过使用其他方法,只是它们不能与 in 关键字一起使用。 例如:- NumberList 实例可通过 each 方法迭代,但在 python 意义上不可迭代。

class NumberList:

     def __init__(self, values):
         self.values = values

     def each(self):
         return self.values

我认为这里的混淆点是,尽管实现 __getitem__ 确实 允许您迭代一个对象,但 不是Iterable 定义的接口的一部分。

abstract base classes 允许一种形式的虚拟子类化,其中 类 实现了指定的方法(在 [=12= 的情况下,只有 __iter__)被考虑isinstanceissubclass 是 ABC 的子类,即使它们没有明确继承它们。它不检查方法实现 是否实际工作 ,只是检查它是否被提供。

有关详细信息,请参阅介绍 ABC 的 PEP-3119


using isinstance(e, collections.Iterable) is the most pythonic way to check if an object is iterable

我不同意;我会使用 duck-typing 并且 尝试迭代对象 。如果对象不可迭代,将引发 TypeError ,如果你想处理不可迭代的输入,你可以在你的函数中捕获它,或者如果不是,则允许渗透到调用者。这完全绕过了对象如何决定执行迭代,而只是找出它是否在最合适的时间执行。


再补充一点,我认为您引用的文档稍微具有误导性。引用 iter docs,也许可以解决这个问题:

object must be a collection object which supports the iteration protocol (the __iter__() method), or it must support the sequence protocol (the __getitem__() method with integer arguments starting at 0).

这清楚地表明,尽管两种协议都使对象可迭代,但只有一个是实际的 "iteration protocol",也就是 isinstance(thing, Iterable)测试。因此我们可以得出结论,在大多数情况下检查 "things you can iterate over" 的一种方法是:

isinstance(thing, (Iterable, Sequence))

虽然这也需要您实现 __len__ 以及 __getitem__"virtually sub-class" Sequence.