如何写一个生成器class?
How to write a generator class?
我看到很多生成器函数的例子,但我想知道如何为 classes 编写生成器。比方说,我想将斐波那契数列写成 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
?
__next__
应该 return 一个项目,而不是放弃它。
您可以编写以下内容,其中 Fib.__iter__
return 是合适的迭代器:
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)
How to write a generator class?
你快到了,写了一个 Iterator class(我在答案的末尾展示了一个生成器),但是 __next__
被调用了每次使用 next
调用该对象时,都会返回一个生成器对象。相反,为了使您的代码以最少的更改和最少的代码行工作,请使用 __iter__
,这会使您的 class 实例化一个 iterable(这是技术上不是 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()
时,它会为我们提供一个 迭代器 :
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
要使 class 本身成为 迭代器 ,它确实需要 __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
只是 returns 实例本身,我们不需要调用它:
>>> f = Fib()
>>> for i in range(3):
... print(next(f))
...
0
1
1
Why is the value self.a not getting printed?
这是你的原始代码和我的评论:
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__
returns:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
Also, how do I write unittest for generators?
您仍然需要为 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
抽象基础 class(请参阅其 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
并使用上面相同的测试:
>>> unittest.main(exit=False)
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
<unittest.main.TestProgram object at 0x00000000031F7CC0>
Python 2
ABCGenerator
只在Python3中,要做到没有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")
注意我直接从Python3standard library复制了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__()
如果你给class一个__iter__()
方法implemented as a generator,“它会自动return一个迭代器对象(技术上,一个生成器对象)”调用时,所以 that 对象的 __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
使该方法成为 生成器 ,并调用该方法 returns 成为 生成器迭代器 . next()
需要一个生成器迭代器来实现 __next__()
和 return
s 一个项目。这就是为什么 yield
ing in __next__()
会导致生成器 class 在调用 next()
时输出生成器迭代器。
https://docs.python.org/3/glossary.html#term-generator
实现接口时,您需要定义方法并将它们映射到您的 class 实现。在这种情况下,__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
我看到很多生成器函数的例子,但我想知道如何为 classes 编写生成器。比方说,我想将斐波那契数列写成 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
?
__next__
应该 return 一个项目,而不是放弃它。
您可以编写以下内容,其中 Fib.__iter__
return 是合适的迭代器:
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)
How to write a generator class?
你快到了,写了一个 Iterator class(我在答案的末尾展示了一个生成器),但是 __next__
被调用了每次使用 next
调用该对象时,都会返回一个生成器对象。相反,为了使您的代码以最少的更改和最少的代码行工作,请使用 __iter__
,这会使您的 class 实例化一个 iterable(这是技术上不是 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()
时,它会为我们提供一个 迭代器 :
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
要使 class 本身成为 迭代器 ,它确实需要 __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
只是 returns 实例本身,我们不需要调用它:
>>> f = Fib()
>>> for i in range(3):
... print(next(f))
...
0
1
1
Why is the value self.a not getting printed?
这是你的原始代码和我的评论:
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__
returns:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
Also, how do I write unittest for generators?
您仍然需要为 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
抽象基础 class(请参阅其 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
并使用上面相同的测试:
>>> unittest.main(exit=False)
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
<unittest.main.TestProgram object at 0x00000000031F7CC0>
Python 2
ABCGenerator
只在Python3中,要做到没有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")
注意我直接从Python3standard library复制了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__()
如果你给class一个__iter__()
方法implemented as a generator,“它会自动return一个迭代器对象(技术上,一个生成器对象)”调用时,所以 that 对象的 __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
使该方法成为 生成器 ,并调用该方法 returns 成为 生成器迭代器 . next()
需要一个生成器迭代器来实现 __next__()
和 return
s 一个项目。这就是为什么 yield
ing in __next__()
会导致生成器 class 在调用 next()
时输出生成器迭代器。
https://docs.python.org/3/glossary.html#term-generator
实现接口时,您需要定义方法并将它们映射到您的 class 实现。在这种情况下,__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