Pythonでのプロパティを使った属性の取得と設定について見ていきましょう。
プログラミングでは外部から直接アクセスできないような非公開にしたい場合があります。Pythonでは全ての属性とメソッドが公開になっているので、外部からのアクセスを制限するためにはプロパティを使います。
その制限された属性を読み書きできるようにセッター、ゲッターを使うと言った話も出てきますが、はっきり言ってちょっと頭が混乱するような難しい内容だとと思います。
プログラミング開発でいずれ直面する内容でもあるので、ここで動きだけでも触れておきましょう。
プロパティによる読み込みのみの属性の設定
クラスの継承で使ったコードで使わない部分を削除して少し書き換えて次のコードにしてみました。
class HondaCar():
def __init__(self, engine=None):
self.engine = engine
def drive(self):
print("車を走らせます。")
def motor_drive(self):
print("モーターで車を走らせます。")
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター", awd=True):
super().__init__(engine)
self.awd = awd
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
nsx_car = NsxCar()
print("エンジン:", nsx_car.engine)
print("AWD:", nsx_car.awd)
実行結果はこうなります。(property.pyのファイル名でAtomで実行)
親クラスを継承していて、初期化メソッドを上書きしています。superで親クラスのメソッドを利用し、独自にself.awdの機能を加えています。
これは継承のところの最後にやった内容と同じですね。(ここではクラスを継承したコードを使って話を進めていますが、単独のクラスのコードでも構いません)
このコードの呼び出し部分、要するに外部からアクセスする部分を次のように書き換えてみまししょう。
nsx_car = NsxCar()
print("エンジン:", nsx_car.engine)
nsx_car.awd = False # デフォルトのTrueからFalseに書き換え
print("AWD:", nsx_car.awd)
外からawdにアクセスして、値をFalseに書き換えています。
当然、実行結果はこうなります。
この外からアクセスして勝手に変更されたくないとこも当然ありますよね。
機能を変えられたく無い。読み込みのみにしたい。ここではそう言う作業を行なって行きます。
まず、初期化メソッドに定義しているself.awdをアンダースコアを使って、self._awdと次のように書き換えます。
class HondaCar():
def __init__(self, engine=None):
self.engine = engine
def drive(self):
print("車を走らせます。")
def motor_drive(self):
print("モーターで車を走らせます。")
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター", awd=True):
super().__init__(engine)
self._awd = awd # アンダースコアを入れる
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
nsx_car = NsxCar()
print("エンジン:", nsx_car.engine)
print("AWD:", nsx_car.awd)
このようにすると、通常の呼び出しでは、nsx_car.awdを読み込むことはできず、エラーになって値が保護されます。
このアンダースコアは通常は変数の使い方としては用いないので、こういう書き方をして見えないようにするわけです。
でも、実際は、呼び出し側でアンダースコアを使って変数を書き換えてやれば変更できてしまいます。
そこでどうやっていくかとなると、デコレータとしてプロパティを使うことになります。
プロパティを使って次のように書き換えます。
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター", awd=True):
super().__init__(engine)
self._awd = awd
@property # デコレータにプロパティを使う
def awd(self):
return self._awd
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
@property置いて、その下に関数を定義しています。関数名はアンダースコア無しのもの。そして、returnでアンダースコアを使った値を返すよう書いていますね。
これを最初のコードのように値を書き換えて呼び出すとエラーになります。
nsx_car = NsxCar()
print("エンジン:", nsx_car.engine)
nsx_car.awd = False
print("AWD:", nsx_car.awd)
一度全体のコードを示しておきます。
class HondaCar():
def __init__(self, engine=None):
self.engine = engine
def drive(self):
print("車を走らせます。")
def motor_drive(self):
print("モーターで車を走らせます。")
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター", awd=True):
super().__init__(engine)
self._awd = awd
@property
def awd(self):
return self._awd
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
nsx_car = NsxCar()
print("エンジン:", nsx_car.engine)
print("AWD:", nsx_car.awd)
書き換えずに呼び出すと元のように結果が表示されます。
このように、値を読み込むことができるけども、書き換えることをできないようにプロパティを使っていきます。
ただし、アンダースコアを明示的に指定して呼び出した場合は書き換えることができます。これは書き換えたというよりも呼び出し時に新たに定義し直していると言う形になるので、データの扱いでは注意が必要です。これはコードを書いていく時に変数名が重なったような場合にわからずに書き換えてしまってるような状態です。開発時にコードの中の他のところでどんな変数をつかっているのか意識して書いてく必要があります。
属性を書き換え可能にするセッター
この読み込み可能にするpropertyの部分をゲッター(getter)と言います。
これを書き換え可能にするには、セッター(setter)を使って行きます。
コードに次のようにセッターを追加していきます。
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター", awd=True):
super().__init__(engine)
self._awd = awd
@property
def awd(self):
return self._awd
@awd.setter # デコレータにセッターを使う
def awd(self, is_off):
self._awd = is_off
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
@awd.setterという風に、プロパティに使われている名前に続けてセッターを書きます。その下にその名前で関数を定義します。書き換え可能な値を示す引数を用意します。ここではawdをオフにできるようにと言う意味でis_offとつけました。これをアンダースコアでつけた変数に代入するというコードになっています。
全体のコードはこうなります。
class HondaCar():
def __init__(self, engine=None):
self.engine = engine
def drive(self):
print("車を走らせます。")
def motor_drive(self):
print("モーターで車を走らせます。")
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター", awd=True):
super().__init__(engine)
self._awd = awd
@property
def awd(self):
return self._awd
@awd.setter
def awd(self, is_off):
self._awd = is_off
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
nsx_car = NsxCar()
print("エンジン:", nsx_car.engine)
nsx_car.awd = False
print("AWD:", nsx_car.awd)
呼び出し部分でで書き換えを行なっています。
実行するとこうなります。
書き換えられているのがわかります。
セッターはゲッターであるプロパティで使われている名前にsetterをつけ、書き換えた値を引数に入れて、それを非公開の変数に入れて、書き換えができたと言う流れです。
条件をつけてプロパティとセッターを使う
ここまでで、デコレータとしてgetterであるプロパティとsetterの使い方を見てきましたが、ちょっとこれは面倒ですよね。書き換えたいなら、さっさと最初のように書き換えちゃった方が楽です。
ではどんな時にプロパティとセッターを使うのかというと、何か条件が合致した時にだけ書き換えることができるというような時にこれを使います。
これまでのコードを使って書き換えてみましょう。該当のクラスを次のように書き換えてみます。
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター",
awd=True,
user="YOU"): # userを設定しました。見やすく改行しています。
super().__init__(engine)
self._awd = awd
self.user = user # selfでuserを保持させています。
@property
def awd(self):
return self._awd
@awd.setter
def awd(self, is_off):
if self.user == "HONDA": # この条件の時に書き換える設定をします。
self._awd = is_off
else: # 条件に合致しない場合の処理です。
print(self.user,"はAWDをOFFにできません")
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
まず、クラスの初期化メソッドに、新たに引数userを設定しています。あなたが車のオーナーということでYOUの値を設定しています。それをselfで保持しています。(見やすくするために引数部分を改行しています)
セッターの部分に条件文を設定しています。userがHONDAの時だけawdを書き換える、つまりここではFalseにすることができる条件にしています。条件に合致しなかった場合に、elseブロックでメッセージを表示させています。
全体のコードはこうなります。
class HondaCar():
def __init__(self, engine=None):
self.engine = engine
def drive(self):
print("車を走らせます。")
def motor_drive(self):
print("モーターで車を走らせます。")
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター",
awd=True,
user="オーナー"):
super().__init__(engine)
self._awd = awd
self.user = user
@property
def awd(self):
return self._awd
@awd.setter
def awd(self, is_off):
if self.user == "HONDA":
self._awd = is_off
else:
print(self.user,"はAWDをOFFにできません")
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
これを使って書き換えの実行をしていきましょう。
まずはこちら。
nsx_car = NsxCar()
print("エンジン:", nsx_car.engine)
nsx_car.awd = False
print("AWD:", nsx_car.awd)
そのままデフォルトで、awdの書き換えを行なってみましょう。
実行結果はこちらです。(呼び出しコードの部分だけ表示しています)
デフォルトではuserはオーナーですがawdもTrueの設定なのでFalseを指定しても書き換えができません。
そこでuserをHONDAに指定して呼び出すコードを書いてみます。
nsx_car = NsxCar(user="HONDA")
print("エンジン:", nsx_car.engine)
nsx_car.awd = False
print("AWD:", nsx_car.awd)
クラスをインスタンス化する時に引数を指定しています。
これを実行するとこうなります。
セッターで設定した条件に合致しているので、Falseに書き換えることができています。
userを別のものに指定してみます。
nsx_car = NsxCar(user="TOYOTA")
print("エンジン:", nsx_car.engine)
nsx_car.awd = False
print("AWD:", nsx_car.awd)
ここではuserをTOYOTAにしてみました。ホンダの車をトヨタに持って行った感じですかね。
実行結果はこうなります。
setterで設定した条件に合致しなかったので、書き換えはできませんでした。
このように、何か条件を加えて、値を書き換えたり、制限を加えたりしたい時にgetter(property)、setterを使うことができるわけです。
クラスの外からのアクセス、内からのアクセス
このように、アンダースコア一個を使ってプロパティと組み合わせて使ってきました。
ではコードのこの部分だけ少し変えてみます。
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター",
awd=True,
user="オーナー"):
super().__init__(engine)
self.__awd = awd # アンダースコアを2つで表記
self.user = user
一つだったアンダースコアを二つにして書き換えました。
これをアンダースコアを二つ付けた次のコードで呼び出して見ます。
nsx_car = NsxCar()
print("AWD:", nsx_car.__awd)
一つのアンダースコアでは、属性の名前を知っていたらアクセスが可能なのですけど、このように二つのアンダースコアを属性につけて呼び出しているわけです。
全コードはこうなります。
class HondaCar():
def __init__(self, engine=None):
self.engine = engine
def drive(self):
print("車を走らせます。")
def motor_drive(self):
print("モーターで車を走らせます。")
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター",
awd=True,
user="オーナー"):
super().__init__(engine)
self.__awd = awd
self.user = user
@property
def awd(self):
return self._awd
@awd.setter
def awd(self, is_off):
if self.user == "HONDA":
self._awd = is_off
else:
print(self.user,"はAWDをOFFにできません")
def drive(self):
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
nsx_car = NsxCar()
print("AWD:", nsx_car.__awd)
実行すると次のようにエラーになります。
このようにアンダースコアが二つの場合は、クラスの外からアクセスできないという形になっています。
これにアクセスするには、クラス内でからアクセスした形にすると可能になります。
次のようにクラスを書き換えて見ます。
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター",
awd=True,
user="オーナー"):
super().__init__(engine)
self.__awd = awd
self.user = user
@property
def awd(self):
return self._awd
@awd.setter
def awd(self, is_off):
if self.user == "HONDA":
self._awd = is_off
else:
print(self.user,"はAWDをOFFにできません")
def drive(self):
print(self.__awd) #ここでアクセスするコードを置いてみます。
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
ここではdrive()のメソッドのところに、アンダースコアを二つにした変数にアクセスするコードを書いています。
これを次のコードで呼び出します。
>nsx_car = NsxCar()
nsx_car.drive()
全体のコードは次のようになります。
class HondaCar():
def __init__(self, engine=None):
self.engine = engine
def drive(self):
print("車を走らせます。")
def motor_drive(self):
print("モーターで車を走らせます。")
class NsxCar(HondaCar):
def __init__(self, engine="3.5L V6 DOHC ツインターボ+3モーター",
awd=True,
user="オーナー"):
super().__init__(engine)
self.__awd = awd
self.user = user
@property
def awd(self):
return self._awd
@awd.setter
def awd(self, is_off):
if self.user == "HONDA":
self._awd = is_off
else:
print(self.user,"はAWDをOFFにできません")
def drive(self):
print(self.__awd)
print("車をスポーティーに走らせます。")
def track_mode(self):
print("サーキット走行モードで走らせます!")
nsx_car = NsxCar()
nsx_car.drive()
実行結果はこうなります。
クラス内で二つのアンダースコアを持つ変数にアクセスしたコードを呼び出す場合は、変数にアクセスできていることがわかります。
まとめ
クラスの属性は、そのままでは変数を直接指定して書き換えることができます。
propertyを設定し、属性を示す変数にアンダースコアを一つ付けることで読み込みのみの属性にすることができます。(ただし、変数名を知っていて明示的にアクセスすると書き換えられます)
これに一部条件を付けて書き換えの制限をする場合には、propertyとsetterをを利用します。
アンダースコアを二つ利用した変数を属性に利用すると、クラスの外からは明示的に指定してもアクセスすることができません。クラス内でアクセスの定義をすると、オブジェクトを生成してからであれば、そこにアクセスすることができます。
ここは初心者としてもちょっと難しいですし、内容的にも混乱しそうな部分でもありますので、いきなりは理解しにくいと思いますが徐々に整理して行きましょう。