ジェネレーター関数の例はたくさんありますが、クラス用のジェネレーターの作成方法を知りたいです。たとえば、フィボナッチ数列をクラスとして書きたいと思います。
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
の書き方は?
ジェネレータークラスの書き方
あなたはほとんどそこにいて、Iteratorクラス(答えの最後にジェネレータを示します)を書いていますが、___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>
_
ジェネレーターオブジェクトを実装し、コレクションモジュールのGenerator
抽象基本クラスを活用しましょう( implementation のソースを参照してください)。つまり、send
およびthrow
-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>
_
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")
_
私はclose
をPython 3 標準ライブラリ から変更せずに直接コピーしたことに注意してください。
__next__
はreturnアイテムであり、アイテムを生成しません。
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)
__next__
関数でyield
を使用せず、python2.7 +との互換性のためにnext
を実装しないでください
コード
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__()
メソッドを指定すると、 ジェネレータとして実装 が呼び出されたときにジェネレータオブジェクトを自動的に返すため、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