我看到很多生成器函数的例子,但我想知道如何为类编写生成器。可以说,我想把斐波纳契系列写成一个类。
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
yield self.a
self.a, self.b = self.b, self.a+self.b
f = Fib()
for i in range(3):
print(next(f))
输出:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
为什么价值self.a
没有被打印出来?另外,如何为生成器编写unittest
?
答案 0 :(得分:33)
如何编写生成器类?
你几乎就在那里,编写一个 Iterator 类(我在答案的最后显示了一个Generator),但是每次调用对象时都会调用__next__
next
,返回一个生成器对象。相反,请使用__iter__
:
>>> class Fib:
... def __init__(self):
... self.a, self.b = 0, 1
... def __iter__(self):
... while True:
... yield self.a
... self.a, self.b = self.b, self.a+self.b
...
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
使类本身成为迭代器:
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
return_value = self.a
self.a, self.b = self.b, self.a+self.b
return return_value
def __iter__(self):
return self
现在:
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
为什么值self.a没有打印?
以下是您的原始代码及其评论:
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
yield self.a # yield makes .__next__() return a generator!
self.a, self.b = self.b, self.a+self.b
f = Fib()
for i in range(3):
print(next(f))
因此,每次调用next(f)
时,都会得到__next__
返回的生成器对象:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
另外,如何为发电机编写unittest?
您仍然需要为Generator
from collections import Iterator, Generator
import unittest
class Test(unittest.TestCase):
def test_Fib(self):
f = Fib()
self.assertEqual(next(f), 0)
self.assertEqual(next(f), 1)
self.assertEqual(next(f), 1)
self.assertEqual(next(f), 2) #etc...
def test_Fib_is_iterator(self):
f = Fib()
self.assertIsInstance(f, Iterator)
def test_Fib_is_generator(self):
f = Fib()
self.assertIsInstance(f, Generator)
现在:
>>> unittest.main(exit=False)
..F
======================================================================
FAIL: test_Fib_is_generator (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<stdin>", line 7, in test_Fib_is_generator
AssertionError: <__main__.Fib object at 0x00000000031A6320> is not an instance of <class 'collections.abc.Generator'>
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
<unittest.main.TestProgram object at 0x0000000002CAC780>
因此,让我们实现一个生成器对象,并利用collections模块中的Generator
抽象基类(参见其implementation的源代码),这意味着我们只需要实现{ {1}}和send
- 免费向我们提供throw
,close
(返回自我)和__iter__
(与__next__
相同)(请参阅{ {3}}):
.send(None)
并使用上述相同的测试:
class Fib(Generator):
def __init__(self):
self.a, self.b = 0, 1
def send(self, ignored_arg):
return_value = self.a
self.a, self.b = self.b, self.a+self.b
return return_value
def throw(self, type=None, value=None, traceback=None):
raise StopIteration
ABC >>> unittest.main(exit=False)
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
<unittest.main.TestProgram object at 0x00000000031F7CC0>
仅适用于Python 3.要在没有Generator
的情况下执行此操作,我们需要至少编写Generator
,close
和__iter__
除了我们上面定义的方法。
__next__
请注意,我直接从Python 3 Python data model on coroutines复制了class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def send(self, ignored_arg):
return_value = self.a
self.a, self.b = self.b, self.a+self.b
return return_value
def throw(self, type=None, value=None, traceback=None):
raise StopIteration
def __iter__(self):
return self
def next(self):
return self.send(None)
def close(self):
"""Raise GeneratorExit inside generator.
"""
try:
self.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
pass
else:
raise RuntimeError("generator ignored GeneratorExit")
,未经修改。
答案 1 :(得分:3)
__next__
应该返回项目,而不是产生它。
您可以编写以下内容,其中Fib.__iter__
返回一个合适的迭代器:
class Fib:
def __init__(self, n):
self.n = n
self.a, self.b = 0, 1
def __iter__(self):
for i in range(self.n):
yield self.a
self.a, self.b = self.b, self.a+self.b
f = Fib(10)
for i in f:
print i
或通过定义__next__
使每个实例本身成为迭代器。
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
x = self.a
self.a, self.b = self.b, self.a + self.b
return x
f = Fib()
for i in range(10):
print next(f)
答案 2 :(得分:3)
不要在yield
函数中使用__next__
并实现next
以兼容python2.7 +
<强>代码强>
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
a = self.a
self.a, self.b = self.b, self.a+self.b
return a
def next(self):
return self.__next__()
答案 3 :(得分:1)
如果您为该课程提供__iter__()
方法implemented as a generator,则会在调用时自动返回生成对象,因此 对象__iter__
将使用__next__
方法。
这就是我的意思:
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
while True:
value, self.a, self.b = self.a, self.b, self.a+self.b
yield value
f = Fib()
for i, value in enumerate(f, 1):
print(value)
if i > 5:
break
输出:
0
1
1
2
3
5
答案 4 :(得分:0)
在方法中使用yield
会使该方法成为 generator ,调用该方法将返回 generator iterator 。 next()
期望生成器迭代器实现__next__()
和return
项。这就是为什么在yield
上进行__next__()
调用时,会导致您的生成器类输出生成器迭代器的原因。
https://docs.python.org/3/glossary.html#term-generator
实现接口时,需要定义方法并将其映射到类实现。在这种情况下,next()
方法需要调用生成器迭代器。
__next__()