如何编写一个生成器类? [英] How to write a generator class?
问题描述
我看到了许多生成器函数的示例,但是我想知道如何为类编写生成器.可以说,我想写斐波那契数列作为一个类.
I see lot of examples of generator functions, but I want to know how to write generators for classes. Lets say, I wanted to write Fibonacci series as a class.
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
?
Why is the value self.a
not getting printed? Also, how do I write unittest
for generators?
推荐答案
如何编写生成器类?
您快到了,正在编写一个 Iterator 类(我在答案的末尾显示了一个Generator),但是每次您使用next
调用该对象时,都会调用__next__
,返回一个生成器对象.相反,要使您的代码以最少的更改和最少的代码行工作,请使用__iter__
,它使您的类实例化一个 iterable (从技术上讲,它不是 generator ):
You're almost there, writing an Iterator class (I show a Generator at the end of the answer), but __next__
gets called every time you call the object with next
, returning a generator object. Instead, to make your code work with the least changes, and the fewest lines of code, use __iter__
, which makes your class instantiate an iterable (which isn't technically a generator):
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()
时,它为我们提供了 iterator :
When we pass an iterable to iter()
, it gives us an iterator:
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
要使类本身成为 iterator ,它确实需要__next__
:
To make the class itself an iterator, it does require a __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
仅返回实例本身,因此我们无需调用它:
And now, since iter
just returns the instance itself, we don't need to call it:
>>> f = Fib()
>>> for i in range(3):
... print(next(f))
...
0
1
1
为什么self.a值无法打印?
这是您的原始代码,上面有我的评论:
Here's your original code with my comments:
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__
返回的生成器对象:
So every time you called next(f)
you got the generator object that __next__
returns:
<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>
因此,让我们实现一个生成器对象,并利用collections模块中的Generator
抽象基类(请参见源代码中的 Python协程数据模型):
So let's implement a generator object, and leverage the Generator
abstract base class from the collections module (see the source for its implementation), which means we only need to implement send
and throw
- giving us close
, __iter__
(returns self), and __next__
(same as .send(None)
) for free (see the Python data model on coroutines):
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
并使用与上面相同的测试:
and using the same tests above:
>>> unittest.main(exit=False)
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
<unittest.main.TestProgram object at 0x00000000031F7CC0>
Python 2
ABC Generator
仅在Python 3中.要在没有Generator
的情况下执行此操作,除了上面定义的方法外,我们还至少需要编写close
,__iter__
和__next__
. /p>
Python 2
The ABC Generator
is only in Python 3. To do this without Generator
, we need to write at least close
, __iter__
, and __next__
in addition to the methods we defined above.
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")
请注意,我直接从Python 3 标准库复制了close
,无需修改.
Note that I copied close
directly from the Python 3 standard library, without modification.
这篇关于如何编写一个生成器类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!