Pythonでオブジェクトのプロパティを使用する
PythonのオブジェクトにおいてC#言語やVala言語にあるようなプロパティ(関連記事)を用いる方法についてを扱う。
プロパティについて再び
オブジェクトのメンバ変数は、(オブジェクト指向における)オブジェクトとその利用側の管理上の理由により、直接外からいじるのではなく、公開されたメンバ関数(アクセサ)を通して読み書きするのが好ましいとされる。また、そうすることにより読み書きに制限を付けることもできるのだが、クラスの外からは関数を呼ぶ形となることもあり、扱いにくい。
プロパティはこの扱いを簡単にし、クラスの外からメンバ変数の扱いに近い感覚でそのメンバ関数を呼び出すことができるようになる。
メモ
objectクラスをもとにしたクラスにおいて、値を格納するメンバとそのメンバの値を読み書きするメンバ関数(アクセサ)を用意し、Pythonの組み込み関数property()でそれらのアクセサを引数に指定した戻り値としてのメンバをプロパティとして用いる。*1
データの書き込みを行うメンバ関数は代入された値を引数として受け取るが、その引数の名前は「value」でなくてもよい。
class TestClass(object): __a = 0 # このメンバは外からは直接参照できない def __init__(self, default_a): self.a = default_a def get_a(self): """ get(読み取り)アクセサ/getter """ return self.__a def set_a(self, new_a): # 代入された値が引数になる """ set(書き込み)アクセサ/setter """ if (new_a > 100): self.__a = new_a else: print '*** WARNING *** new_a(%d) must be greater than 100.' % new_a # プロパティ # クラスの外から「a」の名前で読み書きするように扱う # 内部ではここで関連付けたメンバ関数が呼ばれる a = property(get_a, set_a)
これでクラスを元にして作成されたオブジェクト(インスタンス)側ではメンバaとして値を取り出したり書き込んだりできるようになる。上の例ではsetアクセサで100を超える値のみ受け付け、そうでないときにはメッセージを表示して値は変わらない。
これとは別に、読み取り専用にしたいプロパティがあるとき、同名のメンバ関数*2に「@property」という記述(関数デコレータ)を付けることで、書き込みを試みたときにAttributeError例外を出すようにできる。
class TestClass(object): def __init__(self, default_a): self.__foo = 12345 # 読み取り専用にすることにする # 別の読み取り専用プロパティ @property def foo(self): return self.__foo
上の例ではクラスの外からは[オブジェクト].fooとして(メンバ変数__fooの値の)読み取りができるが、代入はできないようになる。
なお、Pythonのproperty()によるプロパティでは、「オブジェクト作成時にのみ代入可であとは読み取りのみ」といった高度なアクセス制御は行えない。
例
[任意]ファイル名: propertytest.py
#! /usr/bin/python # -*- encoding: utf-8 -*- # Pythonにおけるプロパティのテスト class TestClass(object): __a = 0 # このメンバは外からは直接参照できない def __init__(self, default_a): self.a = default_a self.__foo = 12345 # 読み取り専用にすることにする def get_a(self): """ get(読み取り)アクセサ/getter """ return self.__a def set_a(self, new_a): # 代入された値が引数になる """ set(書き込み)アクセサ/setter """ if (new_a > 100): self.__a = new_a else: print '*** WARNING *** new_a(%d) must be greater than 100.' % new_a # プロパティ # クラスの外から「a」の名前で読み書きするように扱う # 内部ではここで関連付けたメンバ関数が呼ばれる a = property(get_a, set_a) # 別の読み取り専用プロパティ @property def foo(self): return self.__foo print '>>> obj = TestClass(999)' obj = TestClass(999) print 'obj.a:%d\n' % obj.a # 受け付けない範囲を入れても反映されない print '>>> obj.a = 10' obj.a = 10 print 'obj.a:%d\n' % obj.a # 正しい範囲の値を入れると反映される print '>>> obj.a = 123' obj.a = 123 print 'obj.a:%d\n' % obj.a # 読み取り専用プロパティの参照と間違った書き込み print 'obj.foo:%d' % obj.foo print '>>> obj.foo = 10000' try: obj.foo = 10000 except AttributeError, (msg): print 'AttributeError: %s' % msg
下は実行結果。
>>> obj = TestClass(999) obj.a:999 >>> obj.a = 10 *** WARNING *** new_a(10) must be greater than 100. obj.a:999 >>> obj.a = 123 obj.a:123 obj.foo:12345 >>> obj.foo = 10000 AttributeError: can't set attribute
(2010/2/25)オブジェクト初期化時以外にプロパティに書き込めないようにしたい場合、__init__()の中でコンストラクタ引数に受け取った値を対象のメンバに書き込むようにしつつそのメンバは(「@property」による)読み取り専用のプロパティで取り出せるようにすればよい。
#! /usr/bin/python # -*- encoding: utf-8 -*- # オブジェクト初期化時にコンストラクタ引数でメンバに値を設定し # それを読み取り専用プロパティで取り出す形のテスト class TestClass(object): def __init__(self, default_b): self.__b = default_b @property def b(self): return self.__b print '>>> obj = TestClass("TEST TEST TEST")' obj = TestClass("TEST TEST TEST") print 'obj.b:%s' % obj.b print '>>> obj.b = "abc"' try: obj.b = "abc" except AttributeError, (msg): print 'AttributeError: %s' % msg
下は実行結果。
>>> obj = TestClass("TEST TEST TEST") obj.b:TEST TEST TEST >>> obj.b = "abc" AttributeError: can't set attribute
関連記事:
- C#言語とVala言語のプロパティについて
- PyGObjectで gobject.GObjectクラスを継承してGObjectプロパティを用いる(前半)
- PyGObjectで gobject.GObjectクラスを継承してGObjectプロパティを用いる(後半)
- Vala言語とC#言語でのプロパティ値変更時の通知について
使用したバージョン:
- Python 2.6.4