クラス

【Python】クラスのプロパティ(property)の使い方

【Python】クラスのプロパティ(property)の使い方

Pythonのクラスのプロパティ(property)について解説します。

クラスのプロパティ(property)

オブジェクト指向のプログラミングでポイントとなるのはカプセル化です。カプセル化とは、オブジェクト指向プログラミングの中心的な概念です。

カプセル化のためにクラスで定義されたインスタンス変数やメソッドについては、直接アクセスはできないようにし、外部に対して必要な情報のみ特定の処理を通じてアクセスできるようにします。これにより想定しない変数へのアクセスを防止し、想定外のバグ発生を抑制することができます。

Pythonでカプセル化、つまり変数の隠ぺいに関して重要な考え方となるのがクラスのプロパティ(property)です。

C言語やJava等の言語で変数の隠ぺいをする一般的な方法としては、アクセサリーメソッドというメソッドを用意する方法があります。この方法では、例えばget_xxx、set_xxxのようなメソッドを用意して、そのメソッド経由で値の取得、設定します。get_xxxはゲッター、set_xxxはセッターといった呼ばれ方をします。

しかし、この方法は少し直感的ではなく、できれば「xxx=設定値」で設定値を代入するといったようにプログラミングできた方が直感的にも扱いやすくなります。

Pythonではプロパティにより、クラス内部ではメソッドのように定義されているにもかかわらず、外部からのアクセスは変数のようにアクセスできるようになっています。

以降では、プロパティの基本的な使い方について説明していきます。

プロパティ(property)の基本的な使い方

クラス内の変数にアクセスするためのメソッドをクラスに定義するという点では、Pythonのプロパティは、C言語やJavaでのゲッターセッターと考え方は同じです。ただ、Pythonでは以下のようにデコレータをつけることで、プロパティとして定義することができます。

プロパティの定義

ゲッター:@propertyデコレータを記載する

セッター:@<プロパティ名>.setterデコレータを記載する。
※<プロパティ名>の部分には具体的なメソッド名が入ります。

デコレータはメソッドを装飾して機能を付与する仕組みです。これにより各種メソッドがプロパティとして動作するようになります。デコレータの考え方や基本については「デコレータ(decorator)の基本的な使い方」でまとめていますので興味があれば参考にしてください。

では、具体的にプロパティの使用例を見ていきましょう。

class Person:
    def __init__(self, first_name=None, last_name=None, age=None):
        self.__first_name = first_name
        self.__last_name = last_name
        self.__age = age

    @property
    def first_name(self):
        return self.__first_name

    @property
    def last_name(self):
        return self.__last_name

    @property
    def age(self):
        return self.__age

    @first_name.setter
    def first_name(self, value):
        self.__first_name = value

    @last_name.setter
    def last_name(self, value):
        self.__last_name = value

    @age.setter
    def age(self, value):
        if value >= 0:
            self.__age = value
        else:
            raise ValueError(
                "Only values greater than or equal to 0 are allowed."
            )

    def myname(self):
        print(f"私の名前は、{self.__last_name}{self.__first_name}、{self.__age}歳です。")


def main():
    person1 = Person("太郎", "田中", 20)
    print(person1.first_name)
    print(person1.last_name)
    print(person1.age)
    person1.myname()
    print("===")

    person1.first_name = "次郎"
    person1.last_name = "佐藤"
    person1.age = 15
    print(person1.first_name)
    print(person1.last_name)
    print(person1.age)
    person1.myname()


if __name__ == "__main__":
    main()
【実行結果】
太郎
田中
20
私の名前は、田中太郎、20歳です。
===
次郎
佐藤
15
私の名前は、佐藤次郎、15歳です。

上記の例では、インスタンス変数を「__」(アンダースコア×2個)を付けた変数名にしているため、外部から直接は参照できないようになっています。正確には「__」(アンダースコア×2個)をつけるとマングリングと呼ばれる機能で変数名が別のものに置き換わっているためアクセスできなくなります。

ゲッターの定義

ゲッターについては以下のように@propertyデコレータをつけて定義します。

    @property
    def first_name(self):
        return self.__first_name

    @property
    def last_name(self):
        return self.__last_name

    @property
    def age(self):
        return self.__age

例えば、__first_nameのゲッターとしては、first_nameという__first_nameの値を返却するメソッドを用意し、@propertyデコレータをつけます。これにより、呼び出す際には「インスタンス名.first_name」というような形で値を呼び出すことができるようになります。他のlast_nameやageについても同様です。

実際に呼び出している部分が以下の部分です。

    person1 = Person("太郎", "田中", 20)
    print(person1.first_name)
    print(person1.last_name)
    print(person1.age)

このように、実際にはメソッドなのですが見た目上は変数のように値にアクセスができます。

セッターの定義

値を設定するためのセッターの定義では、以下のように@<プロパティ名>.setterデコレータをつけて定義します。

    @first_name.setter
    def first_name(self, value):
        self.__first_name = value

    @last_name.setter
    def last_name(self, value):
        self.__last_name = value

    @age.setter
    def age(self, value):
        if value >= 0:
            self.__age = value
        else:
            raise ValueError(
                "Only values greater than or equal to 0 " "are allowed."
            )

例えば、__first_nameのセッターとしては、first_nameというメソッドでvalueを引数に受けとって__first_nameに値を設定するメソッドを用意し、@first_name.setterデコレータをつけています。これにより呼び出し側で値を設定する際には「インスタンス名.first_name = 設定値」というようにすることができます。

実際に呼び出している部分が以下の部分です。

    person1.first_name = "次郎"
    person1.last_name = "佐藤"
    person1.age = 15

変数に直接値を設定するかのように使用できていることが分かるかと思います。

なお、last_nameやageについてもfirst_nameと同様の考え方ですが、年齢のageについては、0以上の数値のみ許容するようにし、0より小さい値が来たらValueErrorの例外をスローするようにして定義しています。これにより以下のように年齢として不適切な使用をした場合には、結果としてはValueErrorが返ってきます。

person1.age = -1
【実行結果例】
ValueError: Only values greater than or equal to 0 are allowed.

このようにプロパティを用いることで外部から変数を隠ぺいしつつ、想定されない値の代入をクラス内のメソッドで防止することができます。

また、利用者側が直感的に値の参照、代入をできるようにすることができる点でget_xxx、set_xxxというアクセサリーメソッドを用意するよりも便利です。

まとめ

Pythonのクラスのプロパティ(property)について解説しました。

Pythonにおけるクラスのプロパティは、カプセル化により外部に対して必要な情報のみを公開する方法で、想定しない変数へのアクセスを防止し、想定外のバグ発生を抑制することができます。

C言語やJava等の言語では一般的にはゲッター(get_xxx)、セッター(set_xxx)といったアクセサリーメソッドを用意して使用しますが、Pythonではプロパティによりクラス内部ではメソッドのように定義されているにもかかわらず、外部からのアクセスは変数のようにアクセスできるようになります。

具体的には、クラス内にメソッドとしてゲッターとセッターを用意し、ゲッターには@propertyデコレータを、セッターには@<プロパティ名>.setterデコレータをつけて定義します。これにより利用者側からは変数名のようにメソッドにアクセスができます。

セッターでは想定されない値の代入を許さないような処理も記載できるため、プロパティを用いることで外部から変数を隠ぺいしつつ、想定されない値の代入をクラス内のメソッドで防止するといったこともできます。また、利用者側が直感的に値の参照、代入をできるようにすることができる点でget_xxx、set_xxxというアクセサリーメソッドを用意するよりも便利です。

クラスを定義する際には、変数は基本的に隠ぺいし、プロパティによってアクセスできるようにプログラミングするようにしましょう。