web-dev-qa-db-ja.com

ジェネレータークラスの書き方

ジェネレーター関数の例はたくさんありますが、クラス用のジェネレーターの作成方法を知りたいです。たとえば、フィボナッチ数列をクラスとして書きたいと思います。

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の書き方は?

27
Pritam

ジェネレータークラスの書き方

あなたはほとんどそこにいて、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>
_

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

私はcloseをPython 3 標準ライブラリ から変更せずに直接コピーしたことに注意してください。

40
Aaron Hall

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

__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
1
martineau