如何编写一个生成器类?

发布于 2021-01-29 19:35:05

我看到了许多生成器函数的示例,但是我想知道如何为类编写生成器。可以说,我想写斐波那契数列作为一个类。

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
被浏览
82
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    如何编写一个生成器类?

    您快要写完 Iterator
    类了(我在答案末尾显示了Generator),但是__next__每次使用调用对象时都会被调用next,并返回一个generator对象。相反,要使您的代码以最少的更改和最少的代码行工作,请使用__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
    

    当我们向传递一个iterable时iter(),它为我们提供了一个 迭代器

    >>> f = iter(Fib())
    >>> for i in range(3):
    ...     print(next(f))
    ...
    0
    1
    1
    

    为了使类本身成为 迭代器 ,它确实需要一个__next__

    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
    

    现在,由于iter只返回实例本身,因此我们无需调用它:

    >>> f = 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?

    您仍然需要实现一个send和throw方法 Generator

    from collections.abc 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>
    

    因此,让我们实现一个生成器对象,并利用Generatorcollections模块中的抽象基类(请参见其实现的源代码),这意味着我们只需要免费实现sendthrow-免费给我们close__iter__(返回自身)和__next__(与.send(None)
    (请参阅协程Python数据模型):

    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
    

    并使用上述相同的测试:

    >>> unittest.main(exit=False)
    ...
    ----------------------------------------------------------------------
    Ran 3 tests in 0.002s
    
    OK
    <unittest.main.TestProgram object at 0x00000000031F7CC0>
    

    Python 2

    美国广播公司Generator仅在Python
    3.要做到这一点没有Generator,我们需要至少写close__iter__以及__next__除了我们上面定义的方法。

    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")
    

    请注意,我close直接从Python
    3标准库复制而未进行任何修改。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看