如何在Python自定义迭代器类中重置索引属性?

1 投票
2 回答
44 浏览
提问于 2025-04-14 18:29

你知道在Python中,当你遍历一个内置的可迭代对象,比如说一个list时,索引每次都是从0开始的吗:

my_list = [1, 2, 3, 4, 5]

for item in my_list:
    print(item)

for other_item in my_list:
    print(item)

这段代码会打印出15两次。也就是说,当my_list再次被遍历时,它是从列表的开头开始,而不是从5开始。

我实现了一个自己的自定义迭代器类,结构大致是这样的(这是一个简化的例子,只包含与迭代相关的部分):

class MyIterator:
    def __init__(self, my_list: list):
         self.my_list = my_list
         self._iteration_index = 0

    def __iter__(self):
         return self

    def __next__(self):
         element = self.my_list[self._iteration_index]
         self._iteration_index += 1
         
         if self._iteration_index >= len(self.my_list) - 1:
             raise StopIteration
         
         return element

目前,如果我创建一个MyIterator类型的对象,我只能遍历一次。请问有没有地方可以把self._iteration_index重新设置为0,这样每次我遍历它时,都能从self.my_list的开头开始呢?

我使用的是Python 3.11.7

2 个回答

-1

我觉得这个解决方案可以帮到你。

class MyIterator:
    def __init__(self, my_list: list):
        self.my_list = my_list
        self._iteration_index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self._iteration_index >= len(self.my_list):
            self._iteration_index = 0
            raise StopIteration
        element = self.my_list[self._iteration_index]
        self._iteration_index += 1
        return element
1

迭代器不允许像你想的那样重置:

一旦迭代器的 __next__() 方法抛出 StopIteration,它在后续的调用中必须继续抛出这个错误。那些不遵循这个规则的实现被认为是有问题的。

如果你想对你的对象进行两次迭代,你需要使用两个独立的迭代器。

使用两个不同的类。一个类表示你要迭代的对象;另一个类则表示对这个对象的迭代过程。

class MyIterable:
    def __init__(self, my_list: list):
        self.my_list = list

    def __iter__(self):
        return MyIterator(self)


class MyIterator:
    def __init__(self, iterable):
        self.list = iterable.list
        self.index = 0
        self.stopped = False

    def __iter__(self):
        return self

    def __next__(self):
        if self.stopped:
            raise StopIteration

        try:
            x = self.list[self.index]
        except IndexError:
            self.stopped = True
            raise StopIteration

        self.index += 1
        return x

这并不一定是 MyIterator 的最佳实现,但它展示了两个要点:

  1. 迭代器独立跟踪当前的索引,而不依赖于你正在迭代的对象。你可以为同一个对象创建多个同时存在的迭代器。

    x = MyIterable([1,2,3])
    i1 = MyIterator(x)
    i2 = MyIterator(x)
    assert next(i1) == 1
    assert next(i2) == 1
    assert next(i1) == 2
    
  2. 合适的迭代器在抛出 StopIteration 后绝不能再产生值。stopped 标志确保即使在之前尝试获取值后,底层列表被修改了,也不会出错。(在迭代器抛出 StopIteration 之前修改底层列表也是个问题,这就是为什么通常建议不要修改你正在迭代的对象。)


不过,与其自己实现迭代器类,你可以把 __iter__ 变成一个生成器函数,这样每次调用它时都会生成一个 新的 生成器,可以迭代你想要的任何东西。

class MyIterable:
    def __init__(self, my_list: list):
        self.my_list = list

    def __iter__(self):
        yield from self.my_list

撰写回答