使用yield或__next __()进行迭代

时间:2018-04-16 17:16:11

标签: python python-3.x

我正在研究制作可迭代的对象,我想知道这两个选项中哪一个更加pythonic /更好的方式,没有区别或者我对使用yield有错误的想法?对我来说使用yield似乎比较清晰,显然它比使用__next __()更快,但我不确定。

class iterable_class():

    def __init__(self, n):
        self.i = 0
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        if self.i < self.n:
            i = self.i
            self.i += 1
            return i
        else:
            raise StopIteration()

使用yield:

class iterable_class_with_generator():

    def __init__(self, n):
        self.i = 0
        self.n = n

    def __iter__(self):
        while self.i < self.n:
            yield self.i
            self.i += 1

2 个答案:

答案 0 :(得分:8)

一个可观察到的区别是第一个版本实现了一个迭代器(一个具有__next__且其__iter__返回自身的对象),而第二个版本实现了一个 > iterable (一个可以实现__iter__来创建一些迭代器的对象)。在大多数情况下,这并没有什么不同,因为for语句和所有itertools都接受任何迭代。

使用以下代码可以看出差异:

>>> x = iterable_class(10)
>>> next(x)
0
>>> next(x)
1
>>> list(x)
[2, 3, 4, 5, 6, 7, 8, 9]

显然,这不会与iterable_class_with_generator一起使用,因为它没有实现__next__。但是有一个更深层次的区别:因为list(x)接受任何迭代,它首先会调用x.__iter__(),这将iterable_class_with_generator创建一个新的生成器,它将从头开始计数。在答案的最后给出了一个真正的基于生成器的迭代器,但在大多数情况下差异并不重要。

关于是使用生成器还是定义自己的__next__的样式差异,两者都将被识别为正确的Python,因此您应该选择能够更好地维护代码的人或团队。由于生成器版本较短,生成器是一个易于理解的Python习语,我选择那个。

请注意,如果使用生成器实现__iter__,则不需要在实例变量中保持迭代状态,因为生成器会为您执行此操作。然后代码更简单:

class iterable_class_with_generator:
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        for i in range(self.n):
            yield i

<小时/> 最后,这是iterable_class_with_generator的一个版本,它实现了一个在内部使用生成器的真正的迭代器:

class iterable_class_with_generator:
    def __init__(self, n):
        self._gen = self._generate(n)

    def __iter__(self):
        return self

    def __next__(self):
        return next(self._gen)

    def _generate(self, n):
        for i in range(n):
            yield i

答案 1 :(得分:1)

魔鬼在于细节。

chepner已经提到了评论中的重大差异。

  

iterable_class.__iter__每次调用时返回相同的迭代器(即它本身),而iterable_class_with_generator.__iter__每次返回一个新的独立迭代器。

如果你不确切知道发生了什么,这会给你带来惊人的结果。

>>> x = iterable_class_with_generator(5)
>>> it = iter(x)
>>> list(it)
[0, 1, 2, 3, 4]
>>> x.i = 0
>>> list(it)
[]
>>> 
>>> x = iterable_class(5)
>>> it = iter(x)
>>> list(it)
[0, 1, 2, 3, 4]
>>> x.i = 0
>>> list(it)
[0, 1, 2, 3, 4]

正如您所看到的,通过调用iter并使用iterable_class_with_generator实例创建的生成器在引发StopIteration后仍然用尽。

iterable_class实例的迭代器就是那个实例本身,所以摆弄x.i可以改变迭代器的状态。

结论:

如果您想要一个迭代器,请实现__iter__(除return self之外什么也不做)和__next__。如果你想要一个不是迭代器本身的iterable,请实现__iter__并在此方法的主体中返回一个迭代器。

两种方法都不同,当你想要一个不是迭代器的迭代时,微妙的差异可能会让你感到厌烦。