在 Python 中,当 __iter__ 被调用时 "reset" 一个迭代器是不是一个坏习惯?

In Python, is it a bad practice to "reset" an iterator when __iter__ is called?

例如,假设我有一个 class 用于迭代文件中的记录:

class MySpecialFile:
    ...
    def reset(self):
        self._handle.seek(0)

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

编辑:

我几个月后才看到这个问题,我问了它,觉得有点愚蠢:)。

正如下面的答案所写,具有副作用的 __iter__ 方法是一件坏事。如果你想多次迭代你的对象,那么,每次调用 __iter__:

时你只需要 return 一个新的迭代器对象
class IterableFile:
    def __iter__(self):
       return FileIterator(self)

回想起来,这很明显,我想这就是我现在感到愚蠢的原因。不确定我在想什么,但我认为我不愿意有两个单独的 classes 的原因是每个新的迭代器都需要创建一个新的文件处理程序(我的 SpecialFile class 只是一个接口在 text/binary 文件之上)当时我觉得 "excessive" 很奇怪。

任何破坏语言结构预期流程的行为都是危险信号,比 "code smell" 更糟糕。这不是 Python 特有的;它适用于任何语言或系统。

但是,请记住关于 "the hobgoblin of little minds" 的限制:

  • 您的 reset 是否改善了程序流程?
  • 生成的代码是否易于阅读和理解?

如果您已经为必须理解和维护它的人(包括您未来的自己)介绍了这些内容,那么 reset 可能是很好的做法。我持怀疑态度,但我在工作中看到过这种情况。

旁注:为什么在第一次构造迭代器时需要 reset 迭代器?

iter 预计不会有副作用。通过违反这个假设,您的代码会破坏各种各样的事情。比如一个事物是否可迭代的标准测试:

try:
    iter(thing)
except TypeError:
    do_whatever()

将重置您的文件。同样,itertools consume recipe:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is None, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

将产生不正确的文件位置,而不是在 consume(your_file, n) 之后推进 n 条记录。在循环之前用 next 跳过前几条记录也会失败:

f = MySpecialFile(whatever)
next(f) # Skip a header, or try, anyway.
for record in f:
    # We get the header anyway.
    uhoh()