Python中"可迭代"究竟是什么意思?

lai*_*e9m 7 python iterable

首先我要澄清,我不是在问什么是"迭代器".

这就是Python的doc中定义"iterable"这个术语的方式:

iterable
一个能够一次返回一个成员的对象. 迭代的示例包括所有序列类型(例如list,str和tuple)和一些非序列类型,如dict,文件对象,以及使用__iter __()或__getitem __()定义的任何类的对象 方法.Iterables可用于for循环以及需要序列的许多其他地方(zip(),map(),...).当一个可迭代对象作为参数传递给内置函数iter()时,它返回该对象的迭代器.这个迭代器适用于一组值的一次传递.使用iterables时,通常不需要调用iter()或自己处理迭代器对象.for语句会自动为您执行此操作,创建一个临时的未命名变量,以便在循环期间保存迭代器.另请参见迭代器,序列和生成器.

正如其他人所建议的那样,使用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
Run Code Online (Sandbox Code Playgroud)

结果很奇怪: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'
Run Code Online (Sandbox Code Playgroud)

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

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

jon*_*rpe 5

我认为这里的混淆点是,尽管实现__getitem__ 确实允许您迭代对象,但它不是Iterable.

抽象基类允许虚拟的子类,其中实现了指定的方法(在的情况下,类的形式Iterable,只__iter__),被认为是由isinstanceissubclass是的ABC的子类,即使他们没有明确地从他们继承。但是,它不检查方法实现是否实际有效,只检查它是否提供。

有关更多信息,请参阅PEP-3119,其中介绍了 ABC。


usingisinstance(e, collections.Iterable)是检查对象是否可迭代的最 Pythonic 的方法

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


再补充一点,我认为你引用的文档有点误导。引用iterdocs,这可能会澄清这一点:

object必须是一个支持迭代协议(__iter__()方法)的集合对象,或者它必须支持序列协议(__getitem__()从 开始的整数参数的方法0)。

这清楚地表明,虽然这两种协议都使对象可迭代,但只有一个是实际的“迭代协议”,而isinstance(thing, Iterable)测试对象正是这个。因此,我们可以得出结论,在最一般的情况下,检查“可以迭代的事物”的一种方法是:

isinstance(thing, (Iterable, Sequence))
Run Code Online (Sandbox Code Playgroud)

尽管这还需要你来实现__len__沿__getitem__“虚拟子类” Sequence