Python プロパティを使った初期設定

Python学習【365日チャレンジ!】81日目のマスターU(@Udemy11)です。

体重が増えてきたので、意識してウォーキングをしているのですが、田舎はやっぱり星空がきれいなんですよね。

星空

あたりまえにこんな星空があるので、その美しさになかなか気づくことができません。

今いる世界とは違った世界に行くことで、違う世界のことがわかり、そして自分が今までいた世界の素晴らしさに気づくことができます。

心地よいコンフォートゾーンから飛び出して、アウトオブコンフォートゾーンへ踏み出してみてください。

これまで見ていた世界が違ったものに見えるようになるはずです。

それではPython学習をすすめましょう。

昨日の復習

昨日は、メソッドのオーバーライドと親メソッドのsuperについて学習しました。

ベースクラスで定義されたメソッドの値などを上書きするのがオーバーライドで、super()を使うことでベースクラスの一部の引数だけを変更することもできました。

class Car:
    def __init__(self, name=None):
        self.model = name

class ToyotaCar(Car):
    def __init__(self, name='Crown', auto_drive=False):
        super().__init__(name)
        self.drive = auto_drive

toyota_car = ToyotaCar()
print('Name:', toyota_car.model)
print('Auto Drive:', toyota_car.drive)

出力結果

Name: Crown
Auto Drive: False

内容がかなり複雑になっているので、理解するのにかなり時間がかかってしまいますが、ここできちんと理解しておかないと、この先はどんどんわからなくなってしまいます。

super()については、時間をかけて理解したほうがいいので、昨日の記事でしっかりと復習しておきましょう!

それでは本日の学習に入ります。

@propertyの使い方

憶えていますか?

@をつけて処理を行うデコレーター。

@propertyは、組み込み関数propertyをデコレーターにしたものです。

組み込み関数であるpropertyをデコレーターとして活用することで、読み込み専用の属性を持った引数を定義することができます。

class Car:
    def __init__(self,name='NXS', auto_drive=False):
        self.model = name
        self._drive = auto_drive
    
    @property
    def drive(self):
        return self._drive
        
car = Car()
print(car.model)
print(car.drive)

出力結果

NXS
False

4行目のself._driveを6-8行目で新しくdriveメソッドとして@propertyでデコレートすることで、最終的な引数となるauto_driveが書き換えられないようにしています。

ちょっとわかりづらいとおもいますが、Carクラスから作られたcarインスタンスには、car.modelcar._drivecar.driveの3つのクラス変数が定義されていて、@propertyでデコレートされたcar.driveだけが読み込み専用のクラス変数になります。

car.driveには、6-8行目でcar._drive、つまり変数auto_driveが渡されるので、auto_driveを変更することはできないということです。

setterで変更可能にする

@propertyでクラス変数を読み込み専用にすることができるわけですが、変更可能にすることもできます。

class Car:
    def __init__(self,name='NXS', auto_drive=False):
        self.model = name
        self._drive = auto_drive
    
    @property
    def drive(self):
        return self._drive

    @drive.setter
    def drive(self, is_enable):
        self._drive = is_enable
        
car = Car()
print(car.model)
car.drive = True
print(car.drive)

出力結果

NXS
True

@propertyで読み込み専用にしたクラス変数のdriveを10行目で.setterをつけたデコレーターにして、11-12行目で新しい引数を与えてオーバーライドすることで、クラス変数のdriveを変更可能にしています。

条件をつけて書き換え可能に

単純にここまでのことだけを学習していると、

こんな面倒なこと必要?

なんて考えが浮かんできます。

わざわざ読み込み専用にして、それを書き換えられるようにする必要ある?と思っちゃいませんか?

そこは、ちゃんとした理由があって、ある条件を指定して、それが合致したときに書き換え可能にできるというシチュエーションで使えるようにできるんです。

具体的には、パスワードを入力して、あっていれば変更できるというようなパターンです。

class Car:
    def __init__(self, name='NXS', auto_drive=False, passwd='123'):
        self.model = name
        self._drive = auto_drive
        self.passwd = passwd

    @property
    def drive(self):
        return self._drive

    @drive.setter
    def drive(self, is_enable):
        if self.passwd == '234':
            self._drive = is_enable
        else:
            raise ValueError

car = Car('NSX', passwd='234')
print(car.model)
car.drive = True
print(car.drive)

出力結果

NXS
True

2行目の初期設定にpasswd='123'を付け加えて、5行目でクラス変数にpasswdを指定します。

次にsetterの13行目で、passwd234の場合は変数is_enableに書き換え可能にして、それ以外の場合はValueErrorを返すように指定しています。

carインスタンスを生成する際に引数に正しいpasswdを入れているので、20行目でクラス変数のcar.driveTrueに書き換えることができて、最後にTrueと出力されます。

ただ、car._driveは書き換え可能なので、20行目をcar._drive = Trueと変更した場合、passwdが間違っていようが、直接_driveを指定しているので、Trueに書き換えられてしまい、この場合も、最後のcar.driveの出力結果は、Trueになってしまいます。

クラス定義の外から隠す

_@propertyを使って、クラス変数を読み込み専用にしたとしても、直接_をつけてクラス変数を指定すれば、書き換えが可能になってしまいます。

クラス定義の外からクラス変数を書き換えられないようにするためには、頭に__(アンダースコア2つ)をつけることで、クラス定義の外からは存在しない変数にすることができます。

なので、インスタンス生成後に触られたくないという場合は__(アンダースコア・アンダースコア)をつけて、外部からアクセスできないようにします。

class Car:
    def __init__(self, name='NXS', auto_drive=False):
        self.model = name
        self.__drive = auto_drive

    @property
    def drive(self):
        return self.__drive

    @drive.setter
    def drive(self, is_enable):
        self.__drive = is_enable

car = Car()
print(car.__drive)

エラー結果

AttributeError: 'Car' object has no attribute '__drive'

4,8,12行目に__をつけて、クラス定義の外からはアクセスできないようにしています。

なので、さわられたくない度数!?でかんがえると

__ > _ > そのまま

という感じです。

こんがらがってくる

今回も色々試して、頭がこんがらがっています。

確かに、クラスの外からprint出力で、__driveを出力しようとするとアクセスできないのですが、car.__drive = Trueと記述するとエラーが起こらなかったりするんです。

で、car.__driveを出力するとTrueになってるんですが、car.driveFalseのままだったり、よくわからなくなってしまいます。

より具体的な使い方が見えてくると、これがこうで、あれがこうなので、それでいいんだとわかるようになるのかもしれませんが、今の時点ではちょっとこんがらがっています。

ま〜、このまま悩み続けていても前に進めないので、このような使い方があるということを頭に入れて次のレクチャーに進もうと思います。

それでは、明日もGood Python!