Pythonは、オブジェクト指向と呼ばれるプログラミング言語の一つです。
今回は、ゲッター・セッターの利用に関することに紹介します。
インスタンス変数の隠蔽及びゲッター・セッターの必要性について
本題に入る前に、まずは本節の目的をお話したいと思います。
次のプログラムを見てみましょう。
1 2 3 4 5 6 7 8 9 | #クラスの定義 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の値を出力 |
Taro
Jiro
Personクラスのインスタンスに対して、nameという属性の値を直接読み取ったり変更したりを行っています。
特段、通常の動作だと思います。
しかし、このように出来てしまう場合に困るケースが存在します。
外から好き勝手に値を書き換え出来てしまうと、プログラムのバグに繋がってしまいます。
例えばの話として・・・
Studentクラスのインスタンス、つまりとある生徒の属性としてtest_score(テストの点数)というものがあったとします。
このtest_score、値は下限0・上限100という想定で値が設定される属性であったとしましょう。
この属性の値を、『-50(マイナス)』や『1000』という値に書き換えられたとします。あるいは文字でもいいでしょう。
もしもtest_scoreの値を用いて処理されるメソッドがあった場合に・・・何らかの不具合が発生してしまう可能性がありえそうですよね。
test_scoreを引数として大学の合格率を算出するメソッドなんてものを用意していた時には、予期せぬ例外が発生してしまうかもしれません。
つまり、オブジェクトの内部構造を隠蔽し外部からの操作を制御する、ということが必要になるケースがあります。
※詳しくは『オブジェクト指向のカプセル化』という考え方に関わる話になります、詳細は割愛します。
今回はそんなお話になります。では早速見てみましょう。
インスタンス変数の隠蔽とアクセス方法
インスタンス変数の値をクラス外部から読み取りや更新できないように設定することが出来ます。
方法は簡単で、インスタンス変数の名前を、アンダースコア(_)2個の__から始まる名前にしてあげます。
1 2 3 4 5 6 7 | #クラスの定義 class Person(): def __init__( self ,name): self .__name = name #変数名の頭にアンダースコア2個(__)を付ける person1 = Person( "Taro" ) #Personクラスからインスタンスを作成 print (person1.__name) #__name属性の値を出力 |
AttributeError: ‘Person’ object has no attribute ‘__name’
このように、直接アクセスしようとするとエラーが発生します。
属性__nameにアクセスできないのは外からだけであり、次のような同クラス内のメソッドを用いると通常通りアクセスが可能です。
1 2 3 4 5 6 7 8 9 10 | #クラスの定義 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属性の値を出力 |
名前はTaroです
『ゲッター』とは、隠蔽されている変数の値を取得するメソッドになります。
『セッター』とは、隠蔽されている変数に値をセットするメソッドになります。
では、改めて下記を見てみましょう。
シンプルにゲッター、セッターを定義して、使用してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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メソッドで、変更された値を取得 |
Taro
Jiro
インスタンス変数の操作に、ゲッター・セッターにあたる2つのメソッドを使用しました。
これが隠蔽されたインスタンス変数へのアクセス方法としては基本となるのですが・・・
メソッドを実行するという形でアクセスするよりも、実はもっとお手軽な方法が存在します。
それが、『プロパティ』という考え方になります。
プロパティでアクセスする
プロパティというものを使用すると、
“インスタンス名.変数名”
という通常の書き方のような方法で、隠蔽された変数にアクセスすることが可能になります。
メソッドを実行するという書き方よりも、直感的でシンプルな気がしますよね。
以下が書式となります。
1 2 3 4 5 6 7 8 9 | #ゲッターにあたるプロパティ @property def プロパティ名( self ): return 値 #セッターにあたるプロパティ @プロパティ名.setter def プロパティ名( self ,value): self .変数名 = 値 |
では、さっそく例を見てみましょう。
次のStudentクラスには、nameとtest_scoreという2つの属性があります。
それぞれの属性(プロパティ)のゲッターとセッターを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #クラスの定義 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に対しても、同様にアクセスが行えますが省略します。
1 2 3 4 5 6 | #---クラス部分は省略 Student1 = Student( "Taro" , 50 ) #Personクラスからインスタンスを作成 print (Student1.name) #nameの値を取得 Student1.name = "Jiro" #nameの値を変更 print (Student1.name) #変更されたnameの値を取得 |
Taro
Jiro
あたかも直接アクセスしているかのような書き方ですが、実際にはゲッター関数name()とセッター関数name()が呼ばれています。
ですがこのままだと結局は好き勝手な値を放り込めてしまうので、少し工夫してみます。
読み取り専用にする
セッターを定義しないことで、値の変更をさせないようにすることが出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #クラスの定義 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の値を変更を試みる |
AttributeError: can’t set attribute
セッターが無いため、nameの値変更は出来ません。
読み取り専用というような動作になります。
セッターで値のバリデーション
セッターを定義する際に、値のバリデーションを書けます。
これは、セットする値が適しているかどうかの検証になります。
適していない場合には、値はセットされないようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #クラスの定義 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点』という文字列に変更を試みる |
TypeError: test_score must be int
設定される値が数値(int)でない場合には変更不可(例外発生)となるようにしてみました。
test_scoreの値を『20点』というような文字列に変更しようとしたため、失敗していることがわかります。
以上が、プロパティと呼ばれるものを利用した際の動作のまとめでした。
まとめ
インスタンス変数の値をクラス外部から読み取りや更新できないように設定することが出来ます
外部から読み取りや更新を行うには、ゲッター関数セッター関数を介して行う方法以外にも、プロパティでアクセスする方法があります
確認問題
次のようなクラスがあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
以下を実行した時に、それぞれどのような出力が得られるか考えてみましょう。
1 2 | Student1 = Student( "Taro" , 50 ) print (Student1.name) |
1 2 | Student1 = Student( "Taro" , 50 ) Student1.name = "Jiro" |
1 2 | Student1 = Student( "Taro" , 50 ) Student1.test_score = "abc" |
答えは次回の記事の最後に!(準備中)
前回の確認問題の回答例
前回の記事はこちら→【Python連載】メソッドのオーバーライド
次のようなプログラムがあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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です