コンストラクタラッパーの作成 - Rubyist の Python 学習記録(6)

PythonLibrary Python NumPy 2019年 1月 4日

比較ヘルパの追加

  • 今後,多くのテストを簡単に実行するために,MonoImage を複数登録したリスト同士の比較を行いたい. そこで,compare_two_mono_image_list というヘルパメソッドを tests/test_mono_image.py の中に作っておく. 複数のリストを zip で固めることで,イテレータを作成できる. これを (x, y) のタプルで受けて,これらを assertEqual で比較する.
  • with such.A('MonoImage class') as it:
        (中略)
        def compare_two_mono_image_list(array0, array1):
            for (x, y) in zip(array0, array1):
                it.assertEqual(x, y)
  • MonoImage を assertEqual で比較し,比較に失敗した場合には MonoImage のインスタンスが表示される. デフォルトでは以下のようにオブジェクトのアドレスが表示されるため,何が失敗したのかわからない.
  • it.assertEqual(x, y)
    AssertionError: <hkob_pyimage.mono_image.MonoImage object at 0x11586cc88> != <hkob_pyimage.mono_image.MonoImage object at 0x10a539198>
  • Python では __repr__ というインスタンスメソッドを実装することで,オブジェクトの文字列表記を変更できる. Ruby では文字列の中に"#{式}"のようにすると変数を表示できるが,Python ではいくつか方法があるようだ(参考: pythonのprint内で変数に代入されている数字や文字を表示する ). format が一番わかりやすい気がするので,今回はこちらで記述してみた.{}内の番号の引数の数字が表示されるようだ.
  • class MonoImage:
        (中略)
        def __repr__(self):
            return "shape = {0}, bits = {1}, signed = {2}, array = {3}".format(self.shape, self.bits, self.signed, self._array)

コンストラクタラッパーのテスト

これから記述するテストでは MonoImage オブジェクトを用意して比較することが多くなる. 毎回,NumPy オブジェクトから作成すると面倒なので,Python の配列から MonoImage を作るコンストラクタラッパーを用意しておきたい. ここでは,コンストラクタラッパーのテスト及び MonoImage の比較テストを実施する.

  • tests/test_mono_image.py に MonoImage.create_with_array のテストを追加する. このメソッドでは Python array と NumPy の dtype, bits, 符号フラグを受け付ける. ここで,bits, 符号フラグは省略可能とする. bits を省略した場合は dtype の itemsize から推定する. 符号フラグを省略した場合は符号なしとする.
  • with such.A('MonoImage class') as it:
        (中略)
        @it.should('have a constracter wrapper method and a generator constracter method from python array.')
        def test():
            ui8a = [[10, 20, 30], [40, 50, 60]]
            i16a = [[1, -2], [-3, 4], [5, -6]]
            da = [3.1, 1.4, 1.5, 9.2, 6.5, 3.5]
            compare_two_mono_image_list([MonoImage.create_with_array(*x) for x in ((ui8a, np.uint8), (i16a, np.int16, 16, True), (da, np.double, 0))], it.marray)

コンストラクタラッパーの実装

  • まず,ラッパーメソッドを実装する.上記の仕様を元に実装するだけである.
  • class MonoImage:
      (中略)
      @classmethod
      def create_with_array(self, array, dtype, bits=None, signed=False):
          nparray = np.array(array, dtype)
          if bits is None:
              bits = nparray.itemsize * 8
          return MonoImage(nparray, bits, signed)
  • chokidar の結果は以下のようになる. 実装自体はできているが,assertEqual のチェックには失敗している. オブジェクトの比較はオーバーライドしていない場合,オブジェクト自身が同じものかどうかをチェックするためである.
  • change:mono_image.py
    test 0000: should have correct getter properties. (test_mono_image.A MonoImage class) ... ok
    test 0001: should have a constracter wrapper method from python array (test_mono_image.A MonoImage class) ... FAIL
    
    ======================================================================
    FAIL: test 0001: should have a constracter wrapper method from python array (test_mono_image.A MonoImage class)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/Users/hkob/python3/hkob_pyimage/tests/test_mono_image.py", line 40, in test
        compare_two_mono_image_list([MonoImage.create_with_array(*x) for x in ((ui8a, np.uint8), (i16a, np.int16, 16, True), (da, np.double, 0))], it.marray)
      File "/Users/hkob/python3/hkob_pyimage/tests/test_mono_image.py", line 20, in compare_two_mono_image_list
        it.assertEqual(x, y)
    AssertionError: shape = (2, 3), bits = 8, signed = False, array = [[10 20 30] [40 50 60]] != shape = (2, 3), bits = 8, signed = False, array = [[10 20 30] [40 50 60]]
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    FAILED (failures=1)

MonoImage の比較の実装

  • 比較(==)のオーバライドをするには,インスタンスメソッドとして __eq__(self, other) を定義すればよい. MonoImage では,bits が等しいこと,signed が等しいこと,ndarray が等しいことの三つが満足すればオブジェクトが等しいとする. ndarray の == は各要素の等価性を確認するだけである.そこで,全ての要素が等しいかを all() で確認している.
  • class MonoImage:
        (中略)
        # methods
        def __eq__(self, other):
            return self._bits == other._bits and self._signed == other._signed and (self._array == other._array).all()
  • これによりテストは通過した.
  • change:tests/test_mono_image.py
    test 0000: should have correct getter properties. (test_mono_image.A MonoImage class) ... ok
    test 0001: should have a constracter wrapper method from python array (test_mono_image.A MonoImage class) ... ok
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.002s
    
    OK

ジェネレータコンストラクタのテスト

  • 既存の MonoImage オブジェクトと同じ型,ビット数を持つコンストラクタメソッドがあると便利である. 特に名前がついているわけではないが,ここではジェネレータコンストラクタと呼ぶことにする. このライブラリでは,データから新規にオブジェクトを作るクラスメソッドを「create_with_hogehoge」,オブジェクトと同じ型のオブジェクトを作るインスタンスメソッドを「generate_with_hogehoge」という形で命名するように決めておく.
  • この命名規則に従い,generate_with_array というインスタンスメソッドのテストを記述する.先ほどのテストの最後の行に一行追加するだけでよいだろう.
  • compare_two_mono_image_list([mi.generate_with_array(a) for (mi, a) in zip(it.marray, (ui8a, i16a, da))], it.marray)

ジェネレータコンストラクタの実装

  • 実装は create_with_array を呼ぶだけなので簡単である.
  • class MonoImage:
        (中略)
        def generate_with_array(self, array):
            return MonoImage.create_with_array(array, self.dtype, self.bits, self.signed)

長くなったので今日はここまで.