Computed property - Rubyist の Python 学習記録(7)

PythonLibrary Python 2019年 1月 7日

インスタンス変数について

1/3 の記事 で以下のようなコンストラクタを作成した.

import numpy as np

class MonoImage:
    def __init__(self, array, bits=8, signed=False):
        self._array = array
        self._bits = bits
        self._signed = signed
ここで _array, _bits, _signed がインスタンス変数である. Python 環境の下調べ のページでも記述したが,Python にはプライベート変数は存在しない. Python では「_」で始まる変数には外部から直接アクセスしないという紳士協定がある. また,「__」で始まる変数は「_クラス名__変数名」として定義され,外部からはアクセスしにくくなる.

外部からのインスタンス変数へのアクセスについて

言語としてアクセスを禁止はしていないが,このように外部から直接インスタンス変数にアクセスすることはするべきではない. ほとんどの言語では,インスタンス変数へのアクセスは getter や setter という仕組みを使ってアクセスを行う. getter や setter はメソッドなので,値の代入や読み出しの際にフックをすることも可能となる. このようなフックをスキップしてしまうことから,外部だけでなく継承された子クラスからも直接変数を書き換えることはするべきではない.

インスタンス変数へのアクセスについては,比較的頻繁に記述されることから,言語ごとにそれをサポートする仕組みが用意されていることが多い. Ruby では,attr_reader, attr_writer, attr_accessor というメソッドに Symbol を渡すだけで,単純な getter や setter の記述をスキップできる. 一方,Swift では property という仕組みが用意されている.単純に public や private(set) など様々なプロパティ識別子によって,様々なアクセス制限が設定できる. この property にはメソッドを記述することができ,先に示したフック処理も記述可能である. また,インスタンス変数に関係ないオブジェクトに関する属性を返す Computed property という仕組みが言語仕様として用意されている.

Python でも @property デコレータを設定することで,メソッドがプロパティとなる.すなわち,属性に関係ないプロパティ(Computed property)を作成することも可能である. すでに記述した max_value も computed property である.書いていた時には意識していなかったが,つい Swift のつもりで computed property を書いてしまっていた.

class MonoImage:
    (中略)
    @property
    def max_value(self):
        return 0 if self._bits == 0 else (1 << self._bits)-1
Ruby の場合には,プロパティという概念はないが,() が省略できるので,メソッドにした場合でも「オブジェクト名.max_value」とアクセスする. Ruby と異なり,Python では引数がないメソッドも () を省略できないので,メソッドなのかプロパティなのかは意識する必要がある. @property をつけなかった場合「オブジェクト.max_value()」とアクセスするが,@property をつけた場合には,「オプジェクト名.max_value」となる. 何をメソッドにして何をプロパティにするかについては,引数のありなしで判断すればよいと思われる.

読み込みプロパティと Computed property のテスト

今後のメモのために,getter と computed property のテストを記録しておく.今後,プロパティが増えたらここにも記録しておく.

with such.A('MonoImage class') as it:
    (中略)
    @it.should('have correct getter & computed properties.')
    def test():
        it.assertEqual([x.bits for x in it.marray], [8, 16, 0])
        it.assertEqual([x.signed for x in it.marray], [False, True, False])
        it.assertEqual([x.dtype for x in it.marray], [np.uint8, np.int16, np.double])
        it.assertEqual([x.width for x in it.marray], [3, 2, 6])
        it.assertEqual([x.height for x in it.marray], [2, 3, 6])
        it.assertEqual([x.max_value for x in it.marray], [255, 65535, 0])
        it.assertEqual([x.ndim for x in it.marray], [2, 2, 1])
        it.assertEqual([x.max for x in it.marray], [60, 5, 9.2])
        it.assertEqual([x.min for x in it.marray], [10, -6, 1.4])
        it.assertEqual([x.sum for x in it.marray], [210, -1, 25.2])

長くなったので今日はここまで.ただし,プロパティが増えたらこのページは追記する.