我一直在寻找一种编写基于生成器的Python迭代器的好方法。我发现了很多关于迭代器主题的教程,很多关于生成器和yield语句的教程,但没有结合这两者的内容。我已经构建了一个有效的小例子,并想知道是否有更好的方法来做到这一点。
class myIterator :
def __init__(self, n) :
self.last = n
self.myGen = self.myGenerator()
def __iter__(self) :
return self.myGenerator()
def next(self) :
return self.myGen.next()
def myGenerator(self) :
prev = 0
fib = 1
while fib < self.last :
res = fib
yield res
fib = fib + prev
prev = res
raise StopIteration
我在真实世界的程序中使用了这种技术,可以在SQLStatements.py in my Github repository
找到最令人困惑的部分是定义next()函数。显而易见的解决方案在每次调用时都返回了第一个元素。存储包含生成器的实例变量有效,但似乎是一个kludge。
如果你知道一个更好的方法来做这个或一个涵盖这个主题的好教程,请告诉我。
编辑: @MartijnPieters发布的第三个例子完全解决了这个问题。保存self.next中的self.generator.next函数提供了下一个函数。我希望这有助于其他人试图解决这个问题。
答案 0 :(得分:8)
迭代器协议由两部分组成。 __iter__
方法是最重要的方法,当您在对象上使用iter()
时,它应该返回迭代器。
只需将__iter__
的正文替换为myGenerator
;无需提出StopIteration
:
class myIterator:
def __init__(self, n):
self.last = n
def __iter__(self):
prev = 0
fib = 1
while fib < self.last:
res = fib
yield res
fib += prev
prev = res
现在iter(myIterator(10))
是一个迭代器,并且有一个.next()
方法。
如果您希望您的类直接用作迭代器,则需要__iter__
返回self
并提供.next()
方法。
.next()
方法为每个函数体生成一个元素(并使用return somevalue
)。生成器只是一个使用yield
的函数,实际上,这样的方法本身为你实现了迭代器协议(包括.next()
)。
如果你使用.next()
,它将会是这样的:
class myIterator:
def __init__(self, n):
self.last = n
self.prev = 0
self.fib = 1
def __iter__(self):
return self
def next(self):
if self.fib < self.last:
res = self.fib
self.fib += self.prev
self.prev = res
return res
raise StopIteration
或您可以重复使用生成器的.next()
方法:
class myIterator:
def __init__(self, n):
self.last = n
self.next = self.myGenerator().next # Use the generator `.next`
def __iter__(self):
return self
def myGenerator(self):
prev = 0
fib = 1
while fib < self.last:
res = fib
yield res
fib += prev
prev = res
答案 1 :(得分:3)
生成器 已经是迭代器。没有必要包装它:
>>> def gen():
... yield 1
... yield 2
... yield 3
...
>>>
>>> a = gen()
>>> dir(a)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
>>> ai = a.__iter__()
>>> ai
<generator object gen at 0x542f2d4>
>>> a
<generator object gen at 0x542f2d4>
>>> a.next()
1
>>> a.next()
2
>>> a.next()
3
>>> a.next()
Traceback (most recent call last):
File "<string>", line 1, in <module>
StopIteration