Python学習5日目～クラス～

## クラス
オブジェクトの構造を定義するための仕組み。
同じクラスを使えば同じ構造をしたオブジェクトをたくさん作りだすことができる。

例えば
~~~Python:example.py
str(123)
str(456)
~~~
~~~:実行結果
'123'
'456'
~~~
このようにstrという組み込み型のクラスを使えば、文字列型の123と456というオブジェクトが生成される

### オブジェクト
オブジェクトはデータと処理から構成される。
このデータと処理のことを属性といい、それぞれ**データ属性**、**メソッド**と呼ぶ。

### クラスオブジェクトとインスタンスオブジェクト
クラス自体のオブジェクト：クラスオブジェクト
クラスから生成されたオブジェクト：インスタンスオブジェクト

### オブジェクトの生成
~~~Python
class(引数,)
変数 = class(引数,) # オブジェクトを変数に保存する
~~~

### メソッドの呼び出し

~~~Python
オブジェクト.メソッド(引数)

# 例
x = set()
x.add(1)
x.add(2)
x
~~~
~~~:実行結果
{1,2}
~~~

## 独自クラスの定義
~~~Python
class クラス名:
    文
~~~

### データ属性
インスタンスオブジェクトに値を保存できる仕組み。
~~~Python
オブジェクト.データ属性名

# 例
class Food:
    pass

x = Food()
x.name = 'milk'
x.price = '150'
print(x.name , x.price)
~~~
~~~:実行結果
milk 150
~~~

### __init__メソッド
オブジェクトのデータ属性を初期化してくれる

~~~Python
# __init__メソッドの定義
class クラス名:
    def __init__(self,引数,...):
        文

# データ属性の初期化
self.データ属性名 = 値
~~~

例えばさっきのFoodだとデータ属性がリセットされないので__int__メソッドを使って初期化する

~~~Python
class Food:
    def __init__(self,name,price):
        self.name = name
        self.price = price
    pass

x = Food('milk',150)
print(x.name , x.price)

y = Food('egg',100)
print(x.name , x.price)
~~~
~~~:実行結果
milk 150
egg 200
~~~

### 独自メソッドの定義

~~~Python
# メソッドの定義
class クラス名:
    def メソッド名(self,引数,...):
        文
~~~

これを使えば先ほどのクラスもよりスマートになる

~~~Python
class Food:
    def __init__(self,name,price):
        self.name = name
        self.price = price

    def show(self):
        print(self.name , self.price)

x = Food('milk',150)
x.show()

y = Food('egg',100)
y.show()
~~~
~~~:実行結果
milk 150
egg 200
~~~

### マングリング
データ処理を外部から隠蔽し、不用意にオブジェクトが変更されることを防止する

~~~Python
class Food:
    def __init__(self,name,price):
        self.name = name
        self.price = price

    def show(self):
        print(self.name , self.price)

x = Food('milk',150)
x.price //= 2 # データ属性の改変
x.show()
~~~
~~~:実行結果
milk 75
~~~
こんな感じでデータが書き換わってしまうことがあるのでマングリングで防止する

~~~Python
class Food:
    def __init__(self,name,price):
        self._name = name # マングリングは_を付ける
        self._price = price

    def show(self):
        print(self._name , self._price)

x = Food('milk',150)
x.price //= 2 # データ属性の改変>エラーになる
x.show()
~~~
同じデータ属性だがマングリングすることでデータ属性が改変されないように出来る

### クラス属性
クラスのオブジェクトに共通する値を保存するために使用する

~~~Python
class クラス名:
    クラス属性名 = 値

# クラス属性の操作
クラス名.クラス属性名

オブジェクト.クラス属性名
~~~

~~~Python
class Food:
    count = 0

    def __init__(self,name,price):
        self.name = name
        self.price = price
        Food.count += 1

    def show(self):
        print(Food.count , self.name , self.price)

x = Food('milk',150)
x.show()

y = Food('egg',100)
y.show()
~~~
~~~:実行結果
1 milk 150
2 egg 200
~~~
クラス内部にcountというクラス属性を定義

## 派生と継承
**派生**はあるクラスをベースに別のクラスを定義すること。
ベースとなるクラスを**基底クラス**と呼ぶ。
派生クラスは基底クラスが持つ機能(データ属性とメソッド)を引き継ぐ。
これを**継承**と呼ぶ。

例題
* Foodオブジェクトを生成
    * チョコレートを100円、賞味期限180日で初期化
* Toyオブジェクトを生成
    * フィギュア、350円、対象年齢3歳以上で初期化

~~~Python
class Food:
    def __init__(self,name,price,use_by_date):
        self.name = name
        self.price = price
        self.use_by_date = use_by_date

    def show(self):
        print('name:' , self.name)
        print('price:' , self.price)
        print('use_by_date:' , self.use_by_date)

class Toy:
    def __init__(self,name,price,target_age):
        self.name = name
        self.price = price
        self.target_age = target_age

    def show(self):
        print('name:' , self.name)
        print('price:' , self.price)
        print('target_age:' , self.target_age)

x = Food('chocolate',100,180)
x.show()
print()
y = Toy('figure',350,3)
y.show()
~~~
~~~:実行結果
name: chocolate
price: 100
use_by_date: 180

name: figure
price: 350
target_age: 3
~~~

共通する処理が多いので派生して簡略化する
共通する部分を基底クラスとして定義、今回はItemクラスを定義

~~~Python
# 共通する処理を基底クラスでまとめる
class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price

    def show(self):
        print('name:',self.name)
        print('price',self.price)

# 基底クラスItemを使ってFoodを定義
class Food(Item):
    def __init__(self,name,price,use_by_date):
        super().__init__(name,price)
        self.use_by_date = use_by_date

    def show(self):
        super().show()
        print('use_by_date:',self.use_by_date)

# 基底クラスItemを使ってToyを定義
class Toy(Item):
    def __init__(self,name,price,target_age):
        super().__init__(name,price)
        self.target_age = target_age

    def show(self):
        super().show()
        print('target_age:',self.target_age)

x = Food('chocolate',100,180)
x.show()
print()
y = Toy('figure',350,3)
y.show()
~~~
~~~:実行結果
name: chocolate
price 100
target_age: 180

name: figure
price: 350
target_age: 3
~~~

### super関数
単一継承(一つの基底クラスからの継承)の場合、super関数を使うと基底クラスのメソッドを呼び出すことができる
上記のコードの場合、showメソッドをsuper関数を使って呼び出している

### 多重継承
先ほどのコードは単一継承
複数の基底クラスを指定すると**多重継承**になる

例題
先ほどのFoodクラスとToyクラスを基底クラスにする
食品と玩具の機能を持ったShokuganクラスを派生させてみる

この時FoodクラスとToyクラスから派生してShokuganクラスが出来るため、クラスの関係性として**ひし形**になる
このような継承の関係を**菱形継承**、**ダイヤモンド継承**と呼ぶ

~~~Python:example.py
# 共通する処理を基底クラスでまとめる
class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price

    def show(self):
        print('name:',self.name)
        print('price',self.price)

# 基底クラスItemを使ってFoodを定義
class Food(Item):
    def __init__(self,name,price,use_by_date):
        super().__init__(name,price)
        self.use_by_date = use_by_date

    def show(self):
        super().show()
        print('use_by_date:',self.use_by_date)

    def eat(self):
        print('eating',self.name)

# 基底クラスItemを使ってToyを定義
class Toy(Item):
    def __init__(self,name,price,target_age):
        super().__init__(name,price)
        self.target_age = target_age

    def show(self):
        super().show()
        print('target_age:',self.target_age)

    def play(self):
        print('playing with',self.name)

#  Food/Toyクラスを使ってShokuganを定義
class Shokugan(Food,Toy):
    def __init__(self, name, price, use_by_date,target_age):
        self.name = name
        self.price = price
        self.use_by_date = use_by_date
        self.target_age = target_age

    def show(self):
        super().show()

x = Shokugan('chocolate+figure',100,180,3)
x.show()
x.eat()
x.play()
~~~
~~~:実行結果
name: chocolate+figure
price 100
target_age: 3
use_by_date: 180
eating chocolate+figure
playing with chocolate+figure
~~~

### MRO
メソッド解決順序
例えばShokuganクラスは多重継承なので、FoodとToyどちらを先に処理しているのかという問題が発生する

確認方法は以下の通り

~~~Python:example.py
# 共通する処理を基底クラスでまとめる
class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price

    def show(self):
        print('name:',self.name)
        print('price',self.price)

# 基底クラスItemを使ってFoodを定義
class Food(Item):
    def __init__(self,name,price,use_by_date):
        super().__init__(name,price)
        self.use_by_date = use_by_date

    def show(self):
        super().show()
        print('use_by_date:',self.use_by_date)

    def eat(self):
        print('eating',self.name)

# 基底クラスItemを使ってToyを定義
class Toy(Item):
    def __init__(self,name,price,target_age):
        super().__init__(name,price)
        self.target_age = target_age

    def show(self):
        super().show()
        print('target_age:',self.target_age)

    def play(self):
        print('playing with',self.name)

#  Food/Toyクラスを使ってShokuganを定義
class Shokugan(Food,Toy):
    def __init__(self, name, price, use_by_date,target_age):
        self.name = name
        self.price = price
        self.use_by_date = use_by_date
        self.target_age = target_age

    def show(self):
        super().show()

print(Shokugan.__mro__) # MROの確認
~~~
~~~:実行結果
(<class '__main__.Shokugan'>, <class '__main__.Food'>, <class '__main__.Toy'>, <class '__main__.Item'>, <class 'object'>)
~~~

Shokugan>>Food>>Toy>>Itemの順に処理される
つまりShokuganクラスでsuper().show()を実行するとFoodクラスのshowメソッドを呼び出す。