如何编写生成器类?

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

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

62810 次浏览

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

如何编写生成器类?

写一个 迭代器类(我在答案的末尾显示了 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

当我们传递一个迭代到 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>

另外,如何为生成器编写单元测试?

您仍然需要为 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>

因此,让我们实现一个生成器对象,并利用来自集合模块的 Generator抽象基类(参见它的 实施的源代码) ,这意味着我们只需要实现 sendthrow——给我们免费的 close__iter__(返回 self)和 __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>

巨蟒2

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

注意,我直接从 Python3标准图书馆复制了 close,没有修改。

不要在 __next__函数中使用 yield,并实现 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__()

如果你给类一个 __iter__()方法 作为生成器实现,“它会自动返回一个迭代器对象(技术上,一个生成器对象)”,所以 那个对象的 __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

在方法中使用 yield使该方法成为 发电机,调用该方法将返回 发生器迭代器发生器迭代器next()需要一个生成器迭代器,它实现 __next__()return的一个项目。这就是为什么 __next__()中的 yielding 使生成器类在调用 next()时输出生成器迭代器。

Https://docs.python.org/3/glossary.html#term-generator

在实现接口时,需要定义方法并将它们映射到类实现。在这种情况下,__next__()方法需要调用生成器迭代器。

class Fib:
def __init__(self):
self.a, self.b = 0, 1
self.generator_iterator = self.generator()


def __next__(self):
return next(self.generator_iterator)


def generator(self):
while True:
yield self.a
self.a, self.b = self.b, self.a+self.b


f = Fib()


for i in range(3):
print(next(f))
# 0
# 1
# 1