python オブジェクトとクラスについて

自作でMTVモデルを作成する際に、クラスの扱い方について
学んだ為、メモとして残します。

内容は初歩中の初歩な内容です。
学んだことは、随時更新します。

## クラスの定義
```python:sample.py
class Person(object):
    def eat(self):
        print('eat')

person = Person()
person.eat()
```
```terminal:出力結果
eat
```
引数のobjectは無くても良いが、python2系の名残で（コードスタイルとしては）記載している方が良いとのこと。
`person = Person()`でクラスの呼出が可能。

## コンストラクタとデストラクタ
```python:sample.py
class Person(object):
    """コンストラクタ"""
    def __init__(self, food):
        self.food = food
        print('Food is ready')

    def eat(self):
        print('eat {}'.format(self.food))

    """デストラクタ"""
    def __del__(self):
        print('Thanks for the food')

person = Person('apple')
person.eat()
del person # 明示的に記述することもできる
```
```terminal:出力結果
Food is ready
eat apple
Thanks for the food
```
コンストラクタとは、オブジェクトを作る際に最初に呼出されるもの。
反対にデストラクタとは、オブジェクトがなくなる際に呼出されるもの。
コンストラクタの`__init__`メソッドは一番最初に呼ばれる為、初期設定等を記述する場合に用いられる。
他にも`__new__`メソッドなどがある。
`self.food`は他のメソッドからも呼出ができる。


## クラスの継承
```python:sample.py
class Animal(object):
    def eat(self):
        print('eat')

class Monkey(Animal):
    def eat(self, food):
        self.food = food
        print('eat {}'.format(self.food))

class Cheetah(Animal):
    def run(self):
        print('run')

animal = Animal()
animal.eat()
print('###########')
monkey = Monkey()
monkey.eat('banana') # メソッドのオーバーライド
print('###########')
cheetah = Cheetah()
cheetah.eat() # クラスの継承
cheetah.run() # 機能の拡張
```
```terminal:出力結果
eat
###########
eat banana
###########
eat
run
```
親クラスを子クラスの引数に渡せば、親クラスの機能を使うことができる。
親クラスの機能をある場合の時だけに拡張したい場合などに使える。
関数を子クラスで上書きすることもできる。

## superによる親のメソッド呼出
```python:sample.py
class Animal(object):
    def __init__(self, food=None):
        self.food = food

    def eat(self):
        print('eat')

class Monkey(Animal):
    def __init__(self, food):
        super().__init__(food)

    def eat(self):
        print('eat {}'.format(self.food))


animal = Animal()
animal.eat()
print('###########')
monkey = Monkey('banana')
monkey.eat()
```
```terminal:出力結果
eat
###########
eat banana
```
`super()`で親クラスの__init__関数が呼ばれた上で、
子クラスの__init__関数が実行される。

## プロパティ（getter/setter）
```python:sample.py
class Animal(object):
    def __init__(self, food=None, password=None):
        self._food = food
        self.password = password

    """getter"""
    @property
    def food(self):
        return self._food

    """setter"""
    @food.setter
    def food(self, food):
        if self.password == 'pass':
            self._food = food
        else:
            # raise ValueError
            print("エラー") # 確認用

animal = Animal('banana', password='pass')
print(animal.food)
animal.food = "orange"
print(animal.food)
print("##############")
animal2 = Animal('banana')
print(animal2.food)
animal2.food = "orange"
print(animal2.food)
```
```terminal:出力結果
banana
orange
##############
banana
エラー
```
プロパティを用いることで、外部から上書きされたくない変数に読み込みはできるが
値を書き換えてほしくないよという時に明示的に表すことができるとされている。
第三者が、設計者の意図しない操作をしてエラーが発生してしまうような事態を防ぐことができる。
`_変数名`アンダースコアをつけることで、
外部から見えてないていにする意味があるとされている。
実際に動作にはほとんど影響しないので、変数の直接参照はできる。

ゲッターは`@property`を追記し関数名を対象の変数名にする。

セッターはプロパティーを設定してる関数名に`.setter`で設定可能。
ある条件下でのみ上書きしてもいいとする場合に用いるとさらている。

`__変数名`アンダースコア2つをつける手法などもあるが、親クラスと子クラスで
メソッド名が重複した時に、コンフリクトを避ける目的で使ったりするのが一般的とのこと。
詳細は[こちら](https://pep8-ja.readthedocs.io/ja/latest/#id23)で確認してください。

## クラス変数
```python:sample.py
class Person(object):
    eated_list = [] # クラス変数

    def eat(self, food):
        print('eat {}'.format(food))
        self.eated_list.append(food)

personA = Person()
personA.eat("apple")
print(personA.eated_list)

personB = Person()
personB.eat("orange")
print(personB.eated_list)
```
```terminal:出力結果
eat apple
['apple']
eat orange
['apple', 'orange']
```
クラス変数とは全ての生成されるオブジェクトで共有可能な変数。
その為、オブジェクト同士で共有したくない情報も上記のコードの様に共有してしまうリスクがある。
これを回避するには、`__init__`メソッド内に変数を持っていき、
オブジェクトが生成される度に、変数の初期化処理を記載するなどして対応する必要がある。