如何编写生成器类?

时间:2017-03-23 17:56:52

标签: python generator fibonacci

我看到很多生成器函数的例子,但我想知道如何为类编写生成器。可以说,我想把斐波纳契系列写成一个类。

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

5 个答案:

答案 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

实现send和throw方法
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 - 免费向我们提供throwclose(返回自我)和__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

Python 2

ABC >>> unittest.main(exit=False) ... ---------------------------------------------------------------------- Ran 3 tests in 0.002s OK <unittest.main.TestProgram object at 0x00000000031F7CC0> 仅适用于Python 3.要在没有Generator的情况下执行此操作,我们需要至少编写Generatorclose__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__()