【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デコレーターを使うことでもプロパティを定義することができます。

クラスの定義

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を付けたメソッドがgetterとなります。

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

タイトルとURLをコピーしました