我正在研究制作可迭代的对象,我想知道这两个选项中哪一个更加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
答案 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__
并在此方法的主体中返回一个迭代器。
两种方法都不同,当你想要一个不是迭代器的迭代时,微妙的差异可能会让你感到厌烦。