プロパティについて理解したのでまとめる

プロパティについて

研究室で質問されたのでPythonのプロパティについて解説しようと思う。

最初にプロパティとはなにかをIT用語辞典で引いていみると、

プロパティとは、オブジェクト指向プログラミングで使用される オブジェクトが保持している、そのオブジェクトの性質を表すデータ。 例えば、画像データのオブジェクトならば、高さや幅などのデータを プロパティとして持っている。

という定義になっている。 つまり、オブジェクト指向プログラミングでクラスのインスタンスから アクセスできるimg.widthとかimg.heightとかの、widthheightのことである。

しかし、カプセル化の概念からすると、直接プロパティをいじれてしまうのは良くない。 そこで、オブジェクト指向プログラミングでは以下のコードのような アクセサというメソッドとアクセス修飾子を使って、プロパティが 不用意にいじくりまわされるのを防いでいる。以下の例で言うと、 getWidthgetHeightsetWidthsetHeightがアクセサである。

class Image {
    private int width;
    private int height;

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

}

class Spam {
    public static void main(String args[]) {
        Image img = new Image();
        img.setWidth(200);
        img.setHeight(100);
        System.out.println(img.getWidth());
        System.out.println(img.getHeight());
    }
}

JavaC++などではこのようにしてプロパティへアクセス制御をしているが、 ただアクセスを制御したいだけなら、確かに冗長な感じはする。

PythonC#ではプロパティをインスタンス(使う側)からはメンバ変数に見えるが、 クラス(実装)ではコードブロック(メソッド)のようにして制御を行う。

具体的な例をPythonで示してみる。 以下のコードがプロパティを利用してアクセス制御を行っているコードになる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Image(object):
    def __init__(self):
        self._width = 300
        self._height = 400

    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, width):
        self._width = width

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, height):
        self._height = height

if __name__ == '__main__':
    img = Image()
    img.width = 200
    img.height = 100
    print img.width
    print img.height

クラスのメンバである_width_heightに対し、デコレータを利用してwidthメソッドや heightメソッドをproperty型に一度変換している(デコレータに関しては 前の記事を参照)。

propertyの第一引数がgetterになっているため、property型でデコレートした メソッドでは、そのプロパティが返したい値を返している。 また、Pythonのプロパティはsetterメソッドを持っているため、そのsetterメソッドで メソッドをデコレートすることで、img.width = 200のような形でアクセスできるように なっている。

今回の例では代入と読み出しの両方を行っていたが、例えば、 読み出しは許可するが、代入を禁止したい場合、以下のようにsetterメソッドが 呼び出されたら例外を投げるなどをして、プロパティへのアクセス制御が行える。

class Image(object):
    ~~~
    @width.setter
    def width(self, width):
        raise ValueError()
    ~~~

このようにプロパティを制御することで、クラスの設計者が意図しない値の代入や、 読み出し、不用意なプロパティの削除を防ぐことができる。