Developer

【Python連載】ゲッター・セッターの利用
2021.07.27
Lv1

【Python連載】ゲッター・セッターの利用

Pythonは、オブジェクト指向と呼ばれるプログラミング言語の一つです。
今回は、ゲッター・セッターの利用に関することに紹介します。


インスタンス変数の隠蔽及びゲッター・セッターの必要性について

本題に入る前に、まずは本節の目的をお話したいと思います。
次のプログラムを見てみましょう。

#クラスの定義
class Person():
    def __init__(self,name): #初期化メソッド
        self.name = name

person1 = Person("Taro") #Personクラスからインスタンスを作成
print(person1.name)      #nameの値を出力
person1.name = "Jiro"    #nameの値を変更
print(person1.name)      #nameの値を出力
C:\Python> python 13-7-1.py
Taro
Jiro

Personクラスのインスタンスに対して、nameという属性の値を直接読み取ったり変更したりを行っています。
特段、通常の動作だと思います。

しかし、このように出来てしまう場合に困るケースが存在します。
外から好き勝手に値を書き換え出来てしまうと、プログラムのバグに繋がってしまいます。

例えばの話として・・・
Studentクラスのインスタンス、つまりとある生徒の属性としてtest_score(テストの点数)というものがあったとします。
このtest_score、値は下限0・上限100という想定で値が設定される属性であったとしましょう。
この属性の値を、『-50(マイナス)』や『1000』という値に書き換えられたとします。あるいは文字でもいいでしょう。
もしもtest_scoreの値を用いて処理されるメソッドがあった場合に・・・何らかの不具合が発生してしまう可能性がありえそうですよね。
test_scoreを引数として大学の合格率を算出するメソッドなんてものを用意していた時には、予期せぬ例外が発生してしまうかもしれません。

つまり、オブジェクトの内部構造を隠蔽し外部からの操作を制御する、ということが必要になるケースがあります。
※詳しくは『オブジェクト指向のカプセル化』という考え方に関わる話になります、詳細は割愛します。

今回はそんなお話になります。では早速見てみましょう。

インスタンス変数の隠蔽とアクセス方法

インスタンス変数の値をクラス外部から読み取りや更新できないように設定することが出来ます。
方法は簡単で、インスタンス変数の名前を、アンダースコア(_)2個の__から始まる名前にしてあげます。

#クラスの定義
class Person():
    def __init__(self,name):
        self.__name = name  #変数名の頭にアンダースコア2個(__)を付ける

person1 = Person("Taro") #Personクラスからインスタンスを作成
print(person1.__name)    #__name属性の値を出力
C:\Python> python 13-7-2.py
AttributeError: ‘Person’ object has no attribute ‘__name’

このように、直接アクセスしようとするとエラーが発生します。

属性__nameにアクセスできないのは外からだけであり、次のような同クラス内のメソッドを用いると通常通りアクセスが可能です。

#クラスの定義
class Person():
    def __init__(self,name):
        self.__name = name

    def my_introduction(self):
        print(f"名前は{self.__name}です")
        
person1 = Person("Taro")   #Personクラスからインスタンスを作成
person1.my_introduction()  #メソッドを利用して__name属性の値を出力
C:\Python> python 13-7-3.py
名前はTaroです

『ゲッター』とは、隠蔽されている変数の値を取得するメソッドになります。
『セッター』とは、隠蔽されている変数に値をセットするメソッドになります。

では、改めて下記を見てみましょう。
シンプルにゲッター、セッターを定義して、使用してみます。

class Person():
    def __init__(self,name):
        self.__name = name

    def get_name(self):      #ゲッターにあたるメソッド
        return self.__name

    def set_name(self,name): #セッターにあたるメソッド
        self.__name = name

person1 = Person("Taro")   #Personクラスからインスタンスを作成
print(person1.get_name())  #get_nameメソッドで値を取得
person1.set_name("Jiro")   #set_nameメソッドで値を変更
print(person1.get_name())  #get_nameメソッドで、変更された値を取得
C:\Python> python 13-7-4.py
Taro
Jiro

インスタンス変数の操作に、ゲッター・セッターにあたる2つのメソッドを使用しました。

これが隠蔽されたインスタンス変数へのアクセス方法としては基本となるのですが・・・
メソッドを実行するという形でアクセスするよりも、実はもっとお手軽な方法が存在します。
それが、『プロパティ』という考え方になります。

プロパティでアクセスする

プロパティというものを使用すると、
“インスタンス名.変数名”
という通常の書き方のような方法で、隠蔽された変数にアクセスすることが可能になります。

メソッドを実行するという書き方よりも、直感的でシンプルな気がしますよね。

以下が書式となります。

#ゲッターにあたるプロパティ
@property
def プロパティ名(self):
    return 値

#セッターにあたるプロパティ
@プロパティ名.setter
def プロパティ名(self,value):
    self.変数名 = 値

では、さっそく例を見てみましょう。
次のStudentクラスには、nameとtest_scoreという2つの属性があります。
それぞれの属性(プロパティ)のゲッターとセッターを定義します。

#クラスの定義
class Student():
    def __init__(self,name,test_score):
        self.__name = name
        self.__test_score = test_score

    #name属性のゲッター
    @property
    def name(self):
        return self.__name

    #name属性のセッター
    @name.setter
    def name(self,value):
        self.__name = value
    
    #test_score属性のゲッター
    @property
    def test_score(self):
        return self.__test_score
    
    #test_score属性のセッター
    @test_score.setter
    def test_score(self,value):
        self.__test_score = value

基本的な形で、各プロパティのゲッターとセッターを定義してみました。
それでは、nameに対してアクセスしてみましょう。
test_scoreに対しても、同様にアクセスが行えますが省略します。

#---クラス部分は省略

Student1 = Student("Taro",50)   #Personクラスからインスタンスを作成
print(Student1.name)            #nameの値を取得
Student1.name = "Jiro"          #nameの値を変更
print(Student1.name)            #変更されたnameの値を取得
C:\Python> python 13-7-7.py
Taro
Jiro

あたかも直接アクセスしているかのような書き方ですが、実際にはゲッター関数name()とセッター関数name()が呼ばれています。

ですがこのままだと結局は好き勝手な値を放り込めてしまうので、少し工夫してみます。

読み取り専用にする

セッターを定義しないことで、値の変更をさせないようにすることが出来ます。

#クラスの定義
class Student():
    def __init__(self,name,test_score):
        self.__name = name
        self.__test_score = test_score

    #name属性のゲッター
    @property
    def name(self):
        return self.__name
    
#---test_score属性のゲッターセッター省略

Student1 = Student("Taro",50)   #Personクラスからインスタンスを作成
Student1.name = "Jiro"          #nameの値を変更を試みる
C:\Python> python 13-7-8.py
AttributeError: can’t set attribute

セッターが無いため、nameの値変更は出来ません。
読み取り専用というような動作になります。

セッターで値のバリデーション

セッターを定義する際に、値のバリデーションを書けます。
これは、セットする値が適しているかどうかの検証になります。
適していない場合には、値はセットされないようにします。

#クラスの定義
class Student():
    def __init__(self,name,test_score):
        self.__name = name
        self.__test_score = test_score

    #test_score属性のゲッター
    @property
    def test_score(self):
        return self.__test_score
    
    #test_score属性のセッター
    @test_score.setter
    def test_score(self,value):
        if type(value) is not int:      #セットする値が数値(int)ではない場合、意図的に例外を発生させる
            raise TypeError("test_score must be int")
        self.__test_score = value

#---name属性のゲッターセッター省略

Student1 = Student("Taro",50)   #Personクラスからインスタンスを作成
Student1.test_score = "20点"    #nameの値を『20点』という文字列に変更を試みる
C:\Python> python 13-7-9.py
TypeError: test_score must be int

設定される値が数値(int)でない場合には変更不可(例外発生)となるようにしてみました。
test_scoreの値を『20点』というような文字列に変更しようとしたため、失敗していることがわかります。

以上が、プロパティと呼ばれるものを利用した際の動作のまとめでした。


まとめ

インスタンス変数の値をクラス外部から読み取りや更新できないように設定することが出来ます
外部から読み取りや更新を行うには、ゲッター関数セッター関数を介して行う方法以外にも、プロパティでアクセスする方法があります


確認問題

次のようなクラスがあります。

class Student():
    def __init__(self,name,test_score):
        self.__name = name
        self.__test_score = test_score

    #name属性のゲッター
    @property
    def name(self):
        return self.__name
    
    #test_score属性のゲッター
    @property
    def test_score(self):
        return self.__test_score
    
    #test_score属性のセッター
    @test_score.setter
    def test_score(self,value):
        if type(value) is not int:
            raise TypeError("test_score must be int")
        self.__test_score = value

以下を実行した時に、それぞれどのような出力が得られるか考えてみましょう。

Student1 = Student("Taro",50) 
print(Student1.name) 
Student1 = Student("Taro",50) 
Student1.name = "Jiro"
Student1 = Student("Taro",50) 
Student1.test_score = "abc"

答えは次回の記事の最後に!(準備中)

前回の確認問題の回答例

前回の記事はこちら→【Python連載】メソッドのオーバーライド

次のようなプログラムがあります。

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

    def my_introduction(self):
        print(f"名前は{self.name}、年齢は{self.age}です")

class Student(Person):
    def __init__(self,name,age,standard_score): 
        self.name = name
        self.age = age
        self.standard_score = standard_score

    def my_introduction(self):
        print(f"名前は{self.name}、年齢は{self.age}、偏差値は{self.standard_score}です")

person1 = Person("Jiro", "23")
student1 = Student("Taro", 15, 50)
person1.my_introduction()
student1.my_introduction()

21行目、22行目では同名のメソッドが実行されています。
どのような出力結果になるか、考えてみましょう。

・以下が解答になります。
21行目では、スーパークラスのメソッドが実行されます。
22行目では、オーバーライドされたメソッドが実行されます。
つまり、次のような出力となります。

名前はJiro、年齢は23です
名前はTaro、年齢は15、偏差値は50です