見出し画像

Python資格取得への道のり 18日目

更新が途切れてしまいました。
Noteの内容は下書きしていたのに公開せず、就寝。
今日は2日分です。

・プロパティを使った属性の設定

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False):
       #self.model = model
       super().__init__(model)
       self.enable_auto_run = enable_auto_run
   def run(self):
       print('super fast')
   def auto_run(self):
       print('auto run')

nissan = Nissan('Note')
print(nissan.enable_auto_run)

#False

Nissanクラスで定義した通り、「nissan.enable_auto_run」を出力してもFalseになることがわかります。これをTrueに変えるには下記コード

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False):
       #self.model = model
       super().__init__(model)
       self.enable_auto_run = enable_auto_run
   def run(self):
       print('super fast')
   def auto_run(self):
       print('auto run')

nissan = Nissan('Note')
nissan.enable_auto_run = True
print(nissan.enable_auto_run)

 #True

しかし、この「nissan.enable_auto_run」の値を勝手に変更されたくない場合はどうすればいいのか?

その時に「プロパティ」

つまり読み込みはできて、書き込みができない設定にすれば良い。
ステップは2つ

①「self.enable_auto_run」の前に「_」をつけて「self._enable_auto_run」にする

       self._enable_auto_run = enable_auto_run

②def enable_auto_run(self)を定義し、「@property」(デコレーター)をつけて、返り値として「self._enable_auto_run」を返す。

    @property
   def enable_auto_run(self):
       return self._enable_auto_run

全体としてはこのような感じ。

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False):
       #self.model = model
       super().__init__(model)
       self._enable_auto_run = enable_auto_run
   
   @property    
   def enable_auto_run(self):
       return self._enable_auto_run
   
   def run(self):
       print('super fast')
   def auto_run(self):
       print('auto run')

nissan = Nissan('Note')
nissan.enable_auto_run = True
print(nissan.enable_auto_run)

#エラ〜

上記コードはエラーになる。それは、読み込みのみ可であるが「nissan.enable_auto_run = True」と記載してしまっているため、これをコメントアウトして実行すると下記結果が得られる

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False):
       #self.model = model
       super().__init__(model)
       self._enable_auto_run = enable_auto_run

   @property
   def enable_auto_run(self):
       return self._enable_auto_run

   def run(self):
       print('super fast')
   def auto_run(self):
       print('auto run')

nissan = Nissan('Note')
#nissan.enable_auto_run = True
print(nissan.enable_auto_run)

#False

しかし、ある特定の条件のみ書き換えたい場合は下記のようなコードとなる。

まずは読み込み専用から書き込みも可にするには、下記を追加。

   @enable_auto_run.setter
   def enable_auto_run(self, is_enable):
       self._enable_auto_run = is_enable
      

全体だとこんな感じ

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False):
       #self.model = model
       super().__init__(model)
       self._enable_auto_run = enable_auto_run

   @property
   def enable_auto_run(self):
       return self._enable_auto_run

   @enable_auto_run.setter
   def enable_auto_run(self, is_enable):
       self._enable_auto_run = is_enable

   def run(self):
       print('super fast')
   def auto_run(self):
       print('auto run')

nissan = Nissan('Note')
nissan.enable_auto_run = True
print(nissan.enable_auto_run)

#True

これだとどんな条件からでも変更できてしまうので、ここから条件を追加。

新たな引数を追加し、passwdがある特定のものであれば、書き込みができる仕様となりました。
下記コードは書き込み可の場合

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False, passwd='123'):
       #self.model = model
       super().__init__(model)
       self._enable_auto_run = enable_auto_run
       self.passwd = passwd

   @property
   def enable_auto_run(self):
       return self._enable_auto_run

   @enable_auto_run.setter
   def enable_auto_run(self, is_enable):
       if self.passwd == '456':
           self._enable_auto_run = is_enable
       else:
           raise ValueError

   def run(self):
       print('super fast')
   def auto_run(self):
       print('auto run')

nissan = Nissan('Note', passwd='456')
nissan.enable_auto_run = True
print(nissan.enable_auto_run)

#True

下記コードは書き込みエラーの場合

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False, passwd='123'):
       #self.model = model
       super().__init__(model)
       self._enable_auto_run = enable_auto_run
       self.passwd = passwd

   @property
   def enable_auto_run(self):
       return self._enable_auto_run

   @enable_auto_run.setter
   def enable_auto_run(self, is_enable):
       if self.passwd == '456':
           self._enable_auto_run = is_enable
       else:
           raise ValueError

   def run(self):
       print('super fast')
   def auto_run(self):
       print('auto run')

nissan = Nissan('Note', passwd='111')
nissan.enable_auto_run = True
print(nissan.enable_auto_run)

#ValueError

新たな引数をトリガーに書き込みができるかを判断させることができる

ちなみに「_enable_auto_run」に関しては、オブジェクト生成後もこの値を知っている場合は、アクセスができます。

print(nissan._enable_auto_run)

#False

この場合、変数にアクセスできてしまうので注意。これをさせたくない場合は「_enable_auto_run」を「__enable_auto_run」(アンダースコアが2個)とする。

print(nissan.__enable_auto_run)

 #AttributeError:​

この結果、生成したオブジェクトからアクセスができないことがわかります。
しかし、「__enable_auto_run」を使用するとオブジェクトからはアクセスが不可ですが、クラス内からはアクセスができます。

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False, passwd='123'):
       #self.model = model
       super().__init__(model)
       self.__enable_auto_run = enable_auto_run
       self.passwd = passwd

   @property
   def enable_auto_run(self):
       return self._enable_auto_run

   @enable_auto_run.setter
   def enable_auto_run(self, is_enable):
       if self.passwd == '456':
           self._enable_auto_run = is_enable
       else:
           raise ValueError

   def run(self):
       print(self.__enable_auto_run)
       print('super fast')

   def auto_run(self):
       print('auto run')

nissan = Nissan('Note', passwd='456')
nissan.run()

#False
#super fast

どういう仕様にしたいのかでプロパティを使用しつつ、アンダースコアに関しても何個で書いていくのかを決定していく。

・クラスを構造体として扱う時の注意点

__(アンダースコアを2個)を使ったとしてそれを出力をしようとしてもエラーになります。しかし、新たに下記コードを追加した際にはオーバーライドされてしまいます。

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False, passwd='123'):
       #self.model = model
       super().__init__(model)
       self.__enable_auto_run = enable_auto_run
       self.passwd = passwd

   @property
   def enable_auto_run(self):
       return self._enable_auto_run

   @enable_auto_run.setter
   def enable_auto_run(self, is_enable):
       if self.passwd == '456':
           self._enable_auto_run = is_enable
       else:
           raise ValueError

   def run(self):
       print(self.__enable_auto_run)
       print('super fast')

   def auto_run(self):
       print('auto run')

nissan = Nissan('Note', passwd='456')
print(nissan.__enable_auto_run)

#エラー

エラーにならない場合

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')

class Nissan(Car):
   def __init__(self, model='PAD', enable_auto_run=False, passwd='123'):
       #self.model = model
       super().__init__(model)
       self.__enable_auto_run = enable_auto_run
       self.passwd = passwd

   @property
   def enable_auto_run(self):
       return self._enable_auto_run

   @enable_auto_run.setter
   def enable_auto_run(self, is_enable):
       if self.passwd == '456':
           self._enable_auto_run = is_enable
       else:
           raise ValueError

   def run(self):
       print(self.__enable_auto_run)
       print('super fast')

   def auto_run(self):
       print('auto run')

nissan = Nissan('Note', passwd='456')
nissan.__enable_auto_run = 'XXXXXXXXXXXXXXXXXXX'
print(nissan.__enable_auto_run)

#XXXXXXXXXXXXXXXXXXX

「nissan.__enable_auto_run」はnissan.を入力した時点では候補として出てこないが、書き換えられてしまうことはあるので、コードの中身を保持するとともに自分で書き換えられてしまうことを念頭にコードを書く必要がある。

・ダックタイピング

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

   def drive(self):
       if self.age >= 18:
           print('ok')
       else:
           raise Exception('No drive')

class Baby(Person):
   def __init__(self, age=1):
       if age < 18:
           super().__init__(age)
       else:
           raise ValueError

class Adult(Person):
   def __init__(self, age=18):
       if age >= 18:
           super().__init__(age)
       else:
           raise ValueError

baby = Baby()
adult = Adult()

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')
   def ride(self, person):
       person.drive()

car = Car()
car.ride(baby)

#raise Exception('No drive')
#Exception: No drive

エラーが返ってこない場合

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

   def drive(self):
       if self.age >= 18:
           print('ok')
       else:
           raise Exception('No drive')

class Baby(Person):
   def __init__(self, age=1):
       if age < 18:
           super().__init__(age)
       else:
           raise ValueError

class Adult(Person):
   def __init__(self, age=18):
       if age >= 18:
           super().__init__(age)
       else:
           raise ValueError

baby = Baby()
adult = Adult()

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')
   def ride(self, person):
       person.drive()

car = Car()
car.ride(adult)

#ok

なぜ「ok」が表示されたのか。
「adult = Adult()」が呼び出され、次に「class Adult(Person):」が呼び出されます。
その中でデフォルト引数である「age=18」と「super().__init__(age)」より親メソッドである「class Person(object):」にage=18を渡していく。
その状態で「car.ride(adult)」よりclass carのrideメソッドを行う。

rideメソッドには「person.drive()」を行うため、class personのdriveメソッドを行い、ageは18という形で条件分岐をして「ok」と表示される。

・抽象クラス

    def drive(self):
       if self.age >= 18:
           print('ok')
       else:
           raise Exception('No drive')
           

PersonのクラスにあったdriveというメソッドをPersonではなく、BabyやAdultのクラスに書くことだってできる。
その場合は下記コードは削除。

car = Car()
car.ride(adult)

全容は下記の通り。

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

class Baby(Person):
   def __init__(self, age=1):
       if age < 18:
           super().__init__(age)
       else:
           raise ValueError

   def drive(self):
       raise Exception('No drive')

class Adult(Person):
   def __init__(self, age=18):
       if age >= 18:
           super().__init__(age)
       else:
           raise ValueError

   def drive(self):
           print('ok')

baby = Baby()
#baby.drive()
#Exception: No drive

adult = Adult()
#adult.drive()
#ok

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')
   def ride(self, person):
       person.drive()

「baby.drive()」か「adult.drive()」のどちらかを実行してみると先ほどと同様の結果が得られる。

これは最終行にあるperson.drive()があるため、予想通りの結果が得られる。

しかし、Adultクラスに書かれた下記コードをコメントアウトするとどうなるのか?

#   def drive(self):
#           print('ok')

全容として下記となる。

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

class Baby(Person):
   def __init__(self, age=1):
       if age < 18:
           super().__init__(age)
       else:
           raise ValueError

   def drive(self):
       raise Exception('No drive')

class Adult(Person):
   def __init__(self, age=18):
       if age >= 18:
           super().__init__(age)
       else:
           raise ValueError

#    def drive(self):
#            print('ok')

baby = Baby()
#baby.drive()
adult = Adult()
adult.drive()

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')
   def ride(self, person):
       person.drive()

#AttributeError: 'Adult' object has no attribute 'drive'

AttributeErrorという形でAdultにはdriveが定義されていないと指摘される。

その場合、継承するクラスにdriveメソッドというコードを強制的に書かせることができる。

手順としては3つ
①import abcの追加
②親クラスの引数に「(metaclass=abc.ABCMeta)」を追加
③親クラス内に「@abc.abstractmethod」を追加し、継承したクラスに書かせたいメソッド名を定義する。
※メソッドを定義したとしてもここでは、中身はpassでいい。

実際にコードにするとこんな感じ

import abc

class Person(metaclass=abc.ABCMeta):
   def __init__(self, age=1):
       self.age = age

   @abc.abstractmethod
   def drive(self):
       pass

class Baby(Person):
   def __init__(self, age=1):
       if age < 18:
           super().__init__(age)
       else:
           raise ValueError

   def drive(self):
       raise Exception('No drive')

class Adult(Person):
   def __init__(self, age=18):
       if age >= 18:
           super().__init__(age)
       else:
           raise ValueError

   def drive(self):
           print('ok')

baby = Baby()
#baby.drive()
adult = Adult()
#adult.drive()

class Car(object):
   def __init__(self, model=None):
       self.model = model
   def run(self):
       print('run')
   def ride(self, person):
       person.drive()

「baby.drive()」か「adult.drive()」のどちらかを実行してみると先ほどと同様の結果が得られる。

またこの状態で実行してもエラーは起きない。

基本的には抽象クラスは使わない。

今日のまとめ

今日はここまで!昨日は公開設定を押さずに寝てしまいました。

明日でクラスを終え、座学に移っていきます!

この記事が気に入ったらサポートをしてみませんか?