【问题标题】:Creating a non-iterator iterable创建一个非迭代器可迭代
【发布时间】:2021-10-15 07:46:52
【问题描述】:

我在阅读 What exactly are iterator, iterable, and iteration?Build a basic Python iterator 时意识到在实践中我不明白必须如何实现可迭代类。

假设我有以下课程:

class MyClass():
    def __init__(self, num):
        self.num = num
        self.count = 0

    def __len__(self):
        return self.num

    def __iter__(self):
        return self

    def __next__(self):
        if self.count < self.num:
            v = self.count
            self.count += 1
            return v
        else:
            self.count = 0
            raise StopIteration

该类是可迭代的,因为它“有一个返回迭代器的__iter__ 方法”*1MyClass 的对象也是迭代器,因为“迭代器是具有 next (Python 2) 或 __next__ (Python 3) 方法的对象。”*1.到目前为止一切顺利。

让我困惑的是一条评论说“迭代器只应该被迭代一次”*2。我不明白为什么以下 sn-p 会永远卡住:

>>> y = MyClass(5)
>>> print([[i for i in y] for i in y])

当然,解决方法是不重置count 成员:

    def __next__(self):
        if self.count < self.num:
            v = self.count
            self.count += 1
            return v
        else:
            raise StopIteration

但现在列表推导必须在内循环中创建新对象:

>>> y = MyClass(5)
>>> print([[i for i in MyClass(5)] for i in y])
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]

现在,假设我希望能够多次调用我的对象。我尝试使用以下方法实现非迭代器可迭代类:

class MyIterator():
    def __init__(self, num):
        self.num = num
        self.count = 0

    def __len__(self):
        return self.num

    def __iter__(self):
        return self.my_iterator()

    def my_iterator(self):
        while self.count < self.num:
            yield self.count
            self.count += 1
        self.count = 0

这很好用:

>>> x = MyIterator(5)
>>> print(list(x))
[0, 1, 2, 3, 4]
>>> print(list(x))
[0, 1, 2, 3, 4]

但是嵌套理解卡住了:

>>> x = MyIterator(5)
>>> print([[i for i in x] for i in x])

再次修复是删除重置内部计数器的行:

    def my_iterator(self):
        while self.count < self.num:
            yield self.count
            self.count += 1

并更改理解以在内循环中创建新对象:

>>> print([[i for i in MyIterator(5)] for i in x])
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]

但“固定”类不能多次迭代:

>>> x = MyIterator(5)
>>> print(list(x))
[0, 1, 2, 3, 4]
>>> print(list(x))
[]

实现非迭代器可迭代的正确方法是什么(请注意,我*认为我遵循了this answer 中的最后一条评论)?还是 Python 明确不支持这个用例?

编辑:

rubber duck debugging的经典案例,我把最后一堂课改成:

class MyIteratorFixed():
    def __init__(self, num):
        self.num = num

    def __len__(self):
        return self.num

    def __iter__(self):
        return self.my_iterator_fixed()

    def my_iterator_fixed(self):
        count = 0
        while count < self.num:
            yield count
            count += 1

我的错误在于我不需要 count 成员,因为 Python 已经拥有迭代器方法的状态(在本例中是 count 的值)。

>>> x = MyIteratorFixed(5)
>>> print(list(x))
[0, 1, 2, 3, 4]
>>> print(list(x))
[0, 1, 2, 3, 4]
>>> print([[i for i in x] for i in x])
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]

我现在的问题是:

  1. 这是实现非迭代器可迭代的正确方法吗?
  2. 什么时候应该使用迭代器,什么时候应该使用非迭代器可迭代?只是其中一个只被调用一次的区别?
  3. 与迭代器相比,非迭代器可迭代器有哪些缺点?

谢谢!!

【问题讨论】:

  • 问题在于next 不可重入:您试图使用单个属性self.count 来跟踪多个独立迭代器的状态。您最后的尝试是正确的,因为 my_iterator_fixed 返回的 generator 对象通过返回自身正确实现了 __iter__
  • "与迭代器相比,非迭代器可迭代的缺点是什么?"问题是您将这些完全视为独立的事物,但实际上,重点在于 "非迭代器可迭代对象 返回一个保持其自身状态的迭代器 .这正是您遇到的问题。迭代器封装了实现迭代逻辑所需的状态。您的可迭代对象正在使用最终由所有迭代器共享的内部状态

标签: python python-3.x iterator iterable


【解决方案1】:

我的最后一次迭代从this answer得到提示

class MyIterator():
    def __init__(self, num):
        self.num = num

    def __iter__(self):
        count = 0
        while count < self.num:
            yield count
            count += 1

【讨论】:

    【解决方案2】:

    我认为一个非迭代器可迭代的真实示例可能会有所帮助: 我通常使用语言数据,并经常为包含单词、句子、词性标签、句法信息等的文档实现某种容器类,但中心结构通常是一些标记列表:

    class Document:
        def __init__(self, wordlist):
            self.tokens = wordlist
    
    doc = Document(['Hello', 'World', '!'])
    

    每当我需要遍历令牌时,我都可以使用for w in doc.tokens,但这太麻烦了。所以我通常会添加__iter__,它将存储的令牌作为迭代器返回:

    class Document:
        def __init__(self):
            self.tokens = ['Hello', 'World', '!']
            
        def __iter__(self):
            return iter(self.words)
    

    现在我可以做for w in doc:,它可以无限次做,如果循环在两者之间中断,下一次它会从第一个单词重新开始,这种行为看起来很自然。但是对象本身不是迭代器(因为next() 没有实现)。

    【讨论】:

      【解决方案3】:
      1. 是的,这是正确的。

      2. 通常,您希望您的迭代器与被迭代的事物分开:它可以很好地分离关注点。

      3. 如果有的话,缺点很少。 Python 中的大多数可迭代类都不充当它们自己的迭代器。类文件对象(包装已经维护自己的文件指针的文件描述符)是唯一想到的例外。例如,

        >>> type(iter([]))
        <class 'list_iterator'>
        >>> type(iter(()))
        <class 'tuple_iterator'>
        >>> type(iter({}))
        <class 'dict_keyiterator'>
        >>> type(iter(set()))
        <class 'set_iterator'>
        

        所考虑的四种类型都没有通过返回对象本身来实现__iter__;它们都返回一个单独类的实例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-07-24
        • 2018-05-20
        • 1970-01-01
        • 1970-01-01
        • 2010-10-09
        • 2019-06-23
        • 1970-01-01
        • 2018-09-23
        相关资源
        最近更新 更多