Python

【Python】インスタンス変数を安全に扱う方法【プロパティ】

この記事では、Pythonでプロパティを定義する方法を解説します。プロパティを定義することで、より安全性の高いクラスを実装することができます。

それでは、プロパティの使い方を見ていきましょう!

プロパティとは?

プロパティとは、クラス外部からはインスタンス変数のように使用でき、クラス内部ではメソッドのように実装した属性のことです。メソッドを通してアクセスすることで、外部からの不正な書き換えを防いだりできます。

例えば以下のようなコードは外部から不正な値で書き換えられます。

クラスの定義

class Person:

    def __init__(self, name, age):
        self.name = name
        # 三項演算子(ageが0以上ならageを代入し、0未満ならば0を代入する)
        self.age = age if (age > 0) else 0

インスタンス化.・呼び出し

# インスタンス化
p = Person('田中太郎', -1)
print(p.age)
# 0

# 変数を直接書き換えれる
p.age = -1
print(p.age)
# -1

上記のコードでは、Parsonクラス初期化時に三項演算子を使ってageが0以下にならないように定義しています。しかし、外部から直接書き換えることで負の値を代入させることができます。

このように想定外の値で書き換えられてしまうと、どんなバグが起こるかわかりません。なので、直接書き換えれないような工夫が必要となります。

カプセル化する

カプセル化とは、オブジェクトの内容を隠蔽することです。インスタンス変数やメソッドを隠蔽することで、外部からの不正なアクセスを防止することができます。隠蔽した属性に外部からアクセスしたい場合は、メソッドを通すことで安全にアクセスすることができます。

メソッドにはそれぞれ名前が付けられています。

  • setter(セッター): 値をセットするためのメソッド
  • getter(ゲッター): 値を取得するためのメソッド
  • deleter(デリーター): 削除された際に呼び出されるメソッド

先ほどのコードをカプセル化してsettergetterを定義してみます。

クラスの定義

class Person:

    def __init__(self, name, age):
        self.name = name
        self.__age = Person._set_age(age)    # 外部から隠蔽

    # ageの条件を記述したスタティックメソッド
    @staticmethod
    def _set_age(age):
        # 三項演算子(ageが0未満ならば0とする)
        return age if (age > 0) else 0

    # setter
    def set_age(self, age):
        self.__age = Person._set_age(age)

    # getter
    def get_age(self):
        return self.__age

インスタンス化・呼び出し

p = Person('田中太郎', -2)
print(p.get_age())
# 0

# 外部から書き換えようとしてみる
p.__age = -3
print(p.get_age())
# 0

# setterを通した書き換え
p.set_age(17)
print(p.get_age())
# 17

外部から__age変数を書き換えるにはset_ageメソッドを呼び出すしかなくなりました。このように、カプセル化することで安全性の高いクラスを定義できます。

property関数でプロパティを定義する

property関数にgettersetterdeleterを引き渡すことで変数を使う要領でgettersetterを呼び出すことができます。

先ほどのコードにproperty関数を使ってプロパティを追加してみます。

クラスの定義

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # setter
    def set_age(self, age):
        print('setterが呼び出されました!')
        self.__age = age if (age > 0) else o

    # getter
    def get_age(self):
        print('getterが呼び出されました!')
        return self.__age

    # deleter
    def del_age(self):
        print('deleterが呼び出されました!')
        self.__age = None

    # プロパティの作成(引数に getter, setter, deleterの順で渡す)
    age = property(get_age, set_age, del_age)

インスタンス化・呼び出し

p = Person('田中太郎', -1)
print(p.age)
# getterが呼び出されました!
# 0

p.age = 17
# setterが呼び出されました!

print(p.age)
# getterが呼び出されました!
# 17

del p.age
# deleterが呼び出されました!

print(p.age)
# getterが呼び出されました!
# None

p.ageを出力するとgetterが呼び出され、代入するとsetterが呼び出されるようにできました。デリーターはdel文でp.ageを削除した際に呼び出されています。

機能を制限する

引数を省略することで機能を制限することができます。例えば以下のコードではsetterのみを実装しています。

クラスの定義

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # setter
    def set_age(self, age): 
        print('setterが呼び出されました!')
        self.__age = age if (age > 0) else 0

    def output(self):
        print(self.__age)

    # プロパティの作成(セッターのみを渡す)
    age = property(fset=set_age)

インスタンス化・呼び出し

p = Person('田中太郎', -1)
p.output()
# 0

p.age = 17
# setterが呼び出されました!

p.output()
# 17

print(p.age)
# エラー

getterは実装していないのでp.ageを出力しようとするとエラーが投げられます。このように、getterのみやsetterのみを実装することで一部の機能を制限することができます。

propertyデコレーター

propertyデコレーターを使うことでもプロパティを定義することができます。

@propertyを付けたメソッドがgetterとなります。setterdeleterを実装するには@メソッド名.setter@メソッド名.deleterとデコレーターをメソッドに付けることで実装できます。

クラスの定義

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

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

    @age.setter
    def age(self, age):
        self.__age = age if (age > 0) else 0

    @age.deleter
    def age(self):
        self.__age = None

インスタンス化・呼び出し

p = Person('田中太郎', -1)

print(p.age)
# 0

p.age = 17
print(p.age)
# 17

del p.age
print(p.age)
# None

このように、デコレータを使うことでどのメソッドがプロパティなのか分かりやすく定義できます。多くのプロパティを定義する場合は、property関数ではなく、デコレータを使った方が直感的に定義できます。

【おすすめ】Python参考書