【问题标题】:What exactly does "iterable" mean in Python? Why isn't my object which implements `__getitem__()` an iterable?Python中的“可迭代”到底是什么意思?为什么我的实现 `__getitem__()` 的对象不是可迭代的?
【发布时间】:2015-09-26 17:32:02
【问题描述】:

首先我想澄清一下,我不是在问什么是“迭代器”。

这就是 Python 的 doc 中“可迭代”一词的定义方式:

可迭代

一个能够一次返回一个成员的对象。 可迭代对象的示例包括所有序列类型(例如 list、str、 和元组)和一些非序列类型,如字典、文件对象和 您使用 __iter__() 或 __getitem__() 方法定义的任何类的对象。

可迭代对象可用于 for 循环和许多其他地方 需要序列的地方(zip(),map(),...)。当一个可迭代的 对象作为参数传递给内置函数 iter(),它 返回对象的迭代器。这个迭代器适用于一次通过 超过一组值。使用可迭代对象时,通常不会 需要自己调用 iter() 或处理迭代器对象。这 for 语句会自动为您执行此操作,创建一个临时 用于在循环期间保存迭代器的未命名变量。

另请参阅迭代器、序列和生成器。

作为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'

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

那么是我误解了什么还是文档不正确?

【问题讨论】:

  • isinstance 不会检查接口是否正确实现,直到你真正尝试迭代它才会发现,只是适当的方法(在这种情况下only __iter__) 可用。
  • "使用 isinstance(e, collections.Iterable) 是检查对象是否可迭代的最 Pythonic 方式" - 不,我会说 尝试迭代它 b> 是最 Pythonic 的方式!
  • 为了测试某个东西是否是可迭代的,我会在尝试 var = iter(var) 的地方执行 try / except 块,如果它抛出异常,那么它不是可迭代的
  • @jonrsharpe。但这可能会弄巧成拙,因为它可能会消耗可迭代对象。也许更好的检查是尝试iter(x),看看它是否会引发TypeError

标签: python iterable


【解决方案1】:

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

abstract base classes 允许一种形式的虚拟子类化,其中实现指定方法的类(在 Iterable 的情况下,只有 __iter__)被 isinstanceissubclass 视为ABC即使它们没有显式继承。但是,它不检查方法实现是否确实有效,只检查它是否提供。

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


使用isinstance(e, collections.Iterable) 是最pythonic的方式 检查对象是否可迭代

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


再补充一点,我认为您引用的文档有点具有误导性。引用iter docs,也许可以澄清这一点:

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

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

isinstance(thing, (Iterable, Sequence))

虽然这也需要您将__len____getitem__ 一起实现为“虚拟子类” Sequence

【讨论】:

  • Thx jorsharpe,您的回答很有帮助。现在,我真的认为我引用的文档具有误导性。词汇表文档中的词汇表“可迭代”意味着“可以迭代”,无论它是如何完成的,而 Python 的 collections.abc.Iterable 代表有效的迭代容器(我编造了这个词),即定义 __iter__ 方法的任何对象。
  • 所以collections.abc.Iterable 不一定是“可迭代的”,因为它可能返回一个不实现__next__ 的无效迭代器。同时,“可迭代”对象可能与collections.abc.Iterable 没有关系,只定义__getitem__。你怎么看?
  • @laike9m 没错,但是简单地定义一个__getitem__ 方法也不能保证它确实有效!这就是为什么我更喜欢使用鸭子输入 "EAFP" 方法的原因 - 鉴于在你这样做之前你无法真正知道一个对象是否实际上可迭代,那么这是处理它的最合适时间而不是存在。理想情况下,文档应该说类似“如果一个对象实现了迭代器或序列协议,则它是可迭代的”,尽管在​​实践中似乎不需要__len__
【解决方案2】:

一个可迭代的。但是,您还没有继承自 abc.Iterable,因此 Python 自然不会报告它是从该类继承的。这两件事 - 作为一个可迭代的,以及从该基类下降 - 是完全独立的。

【讨论】:

  • 我认为 OP 的困惑在于实现 __iter__ 的类是 Iterable 的虚拟子类,即使它们没有显式继承自它。
【解决方案3】:

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

所以,检查对象是否可迭代的大多数“鸭式”方法是对象是否是这样的,(是的,我隐含地知道 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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2019-02-20
  • 2013-07-11
  • 2020-04-09
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
相关资源
最近更新 更多