自定义迭代器类与生成器?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义迭代器类与生成器?相关的知识,希望对你有一定的参考价值。

以下是说明。我想知道在可以定义生成器函数时是否有理由定义自定义迭代器类。

我需要迭代一个将每个元素转换为int的序列,例如

# seq is a sequence of strings or in general anything convertible to int
def f(seq):
    # ...
    g(int_iter(seq))

# iseq is a numeric sequence
def g(iseq):
    it = iter(iseq)
    # ...

我可以使用自定义迭代器类:

# iterator converting elements it iterates over to int
class int_iter:
    def __init__(self, iterable):
        self.it = iter(iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return int(next(self.it))

或发电机功能:

def int_iter(seq):
    return (int(i) for i in seq)

这些解决方案总是可以互换吗? 它们是否等效(时间和空间)? 在风格上,他们中的任何一个被认为更好吗?

谢谢!

答案

如果我打算将其作为答案编写,那么让我们添加一些示例来演示差异。假设我们有一个简单的迭代:

source_list = list(range(10))  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

如果你想把它变成一个字符串列表,除了已经提到的为此目的设计的map()之外还有很多方法 - 你可以做一个简单的生成器:

def gen_str(iterable):  # this is equivalent to returning an in-line generator
    for element in iterable:
        yield str(element)

test_gen = gen_str(source_list)
for element in test_gen:
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 

或者你可以编写一个完整的迭代器类:

class iter_str(object):

    def __init__(self, iterable):
        self._iterable = iterable
        self._iterator = self._get_iter(self._iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return str(next(self._iterator))

    @staticmethod
    def _get_iter(iterable):  # a generator for forward iteration
        for element in iterable:
            yield element

test_iter = iter_str(source_list)
for element in test_iter:
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 

到目前为止,它们是相同的 - 但是如果你想在迭代时跳过一些元素会发生什么?你不能指示生成器这样做,并且为了跳过你需要在迭代代码本身中添加跳过耗尽逻辑的元素,即:

test_gen = gen_str(source_list)
for element in test_gen:
    if element == "5":
        for _ in range(3):
            next(test_gen)
        continue
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '9' 

另一方面,使用迭代器类,您可以通过添加一个简单的skip()方法来封装您的控件,如:

def skip(self, elements=1):
    for _ in range(elements):
        next(self._iterator)

然后你可以优雅地做同样的事情:

test_iter = iter_str(source_list)
for element in test_iter:
    if element == "5":
        element = test_iter.skip(3)
        continue
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '9' 

但这只是冰山一角 - 如果你想在迭代中途停止生成字符串并使用原始数据会发生什么?没有办法通知生成器这样做(除非你通过传递一些外部控制变量来构建它),而对迭代器类的简单更改允许你这样做:

class iter_str(object):

    def __init__(self, iterable, string_mode=True):
        self._iterable = iterable
        self.string_mode = string_mode
        self._iterator = self._get_iter(self._iterable)

    def __iter__(self):
        return self

    def __next__(self):
        element = next(self._iterator)
        if self.string_mode:
            return str(element)
        return element

    @staticmethod
    def _get_iter(iterable):  # a generator for forward iteration
        for element in iterable:
            yield element

test_iter = iter_str(source_list)
for element in test_iter:
    if element == "4":
        test_iter.string_mode = False
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' 5 6 7 8 9 

通过这种方式,您可以对迭代添加任意控制,包括反转,重复迭代,甚至在迭代中途切换迭代器源等。简单的生成器不允许您在没有重大麻烦的情况下执行任何操作。

至于效率,从这个例子中可以明显看出,发电机效率更高,因为我们仍然依赖于内部发电机,但如果你需要控制你所需要的迭代的生命周期,性能损失将很快消失。添加更复杂的检查,通常会让您的生活变得悲惨,试图解决发电机限制问题。

我不会评论风格,但我声称,一般来说,最好使用最好的工具 - 如果你不需要生命周期控制你的iterable,继续使用生成器,如果你这样做 - 迭代器类是一种方法。

另一答案

这一切都取决于您期望从对象中获得的功能。如果你只想要一次性迭代作为结果,这是在内存方面优化而不是像列表,元组等容器,最好的方法是使用生成器表达式。如果您希望您的对象可以多次迭代,您应该使用列表推导或其他等价物(设置理解等)。

如果您想要容器或生成器无法满足的更多功能,您应该使用自定义对象并将您期望的功能添加为类的不同方法。

以上是关于自定义迭代器类与生成器?的主要内容,如果未能解决你的问题,请参考以下文章

如何从片段中调用 getSupportFragmentManager()?

31 迭代器 Iterator 是什么?

迭代器模式以及对内部类的运用

在 C++ 中为我自己的自定义向量模板类实现迭代器(类似于 STL)[重复]

C++ 迭代器:实现迭代器类时出错

VS Code中自定义Emmet代码片段