Pythonのクラスについての知識をまとめてみた

Pythonのクラスについての理解が浅いため、業務で活用できるよう勉強した。勉強資料としては主にYouTubeと公式ドキュメントを使用した。（[参照](#参照)）
# 目次
[(1)クラスとは](#1-クラスとは)
[(2)クラスの表現](#2-クラスの表現)
[(3)Constructorの実装](#3-Constructorの実装)
[(4)Finalizerの実装](#4-Finalizerの実装)
[(5)継承](#5-継承)
[(6)クラスメソッド](#6-クラスメソッド)
[(7)スタティックメソッド](#7-スタティックメソッド)


## 1. クラスとは
クラスとは、概念として存在するオブジェクトをインスタンスとして実際に作成するための仕組みである。
クラスの説明をするときは、よく鯛焼き機が例に出される。クラスの概念を鯛焼き機の例に当てはめると以下のようになる。
||例|
|:-:|:-:|
|オブジェクト|鯛焼きという概念|
|クラス|鯛焼き機|
|インスタンス|鯛焼き|

とにかく、クラスという設計図を作成することで、その設計図のルールに則ったインスタンスという実態を簡単に作成できるようになる。

## 2. クラスの表現
クラスは「データ」と「処理」によって表現される。
Pythonではこれらのことを特に「クラス変数、インスタンス変数」「メソッド」と呼ぶ。
||Pythonでの呼び方|説明|
|:-:|:-:|:-:|
|データ|クラス変数|クラス内で定義された変数|
|データ|インスタンス変数|インスタンス内で定義された変数|
|処理|メソッド|クラス内で定義された関数|

それでは、実際にクラスを定義してみる。
```python:クラスの定義
class Taiyaki: #クラスを定義

    feature = "oishii!!" #クラス変数を定義
    
    def get_price(self,price,tax): #メソッドを定義
        print(price*tax)

t_1 = Taiyaki() #インスタンス化

print(t_1.feature)
# > oishii!!

t_1.store = "shibuya" #アトリビュートを定義
print(t_1.store)
# > shibuya

t_1.get_price(150,1.08) #メソッド実行
# > 162.0
```
### クラスを定義
```python
class Taiyaki: #クラスを定義
```
`class`に続いてクラス名を記述することでクラス`Taiyaki`が定義される。

### クラス変数を定義
```python
class Taiyaki: #クラスを定義

    feature = "oishii!!" #クラス変数を定義

print(t_1.feature)
# > oishii!!
```
クラス変数は全てのインスタンスで共有される。インスタンスから呼び出す場合、`t_1.feature`と()無しで記述すれば良い。
### アトリビュートを定義
```python
t_1.store = "shibuya" #アトリビュートを定義
print(t_1.store)
# > shibuya
```
アトリビュートは各インスタンスに対して定義する。インスタンス`t_1`にアトリビュート名`store`を繋ぎ、変数を代入する。
このようにインスタンスを作成するたびにアトリビュートを定義するのは、かなり面倒である。
この問題を解消するメソッドとしてConstructorというものがある。Constructorの概要については後述を参照してほしい（[3. Constructorの実装](#3-Constructorの実装))。

:::note warn
クラス変数とインスタンス変数で同じ名前を使用した場合、インスタンス変数が優先される。
:::

### メソッドを定義
```python
def get_price(self,price,tax): #メソッドを定義
    print(price*tax)
```
メソッドは、関数と同様に`def`で書き始めて定義する。
ほとんど関数と同様の構造であるが、注意点として、メソッドの引数には`self`を記述しなければならない。`self`とはインスタンス自身のことである。つまり、`t_1.price()`は`t_1.price(t_1)`という実行内容を省略したものになる。私たちは普段意識していないだけで普段からこのようにインスタンスを省略してメソッドを使用している。Pythonの組み込み関数を使用して例を挙げる。
```python:組み込み関数
sum([1,2,3])
# > 6

a_list = [1,2,3]
a_list.sum()
# > 6
```
`sum()`は本来、括弧内の第一引数の数値列を合計するものである。しかし、インスタンスを通して呼び出したときは、そのインスタンスを計算対象として扱う（つまり`self`を第一引数として扱う）と考えるように実行される。このとき、`a_list.sum(a_list)`と書くと見づらいため、引数となるインスタンスは省略して良い、という仕様になっている。
まとめると、Pythonではメソッド内で`self`を定義することで引数にオブジェクトを書く手間を省くことができる。

## 3. Constructorの実装
Constructorとは、メソッドの一種であり、`__init__()`というメソッド名で定義する。このメソッドは、インスタンスを作成したときに自動で呼び出されるため、クラスの初期設定に有用である。
先ほど作成したクラス`Taiyaki`にConstructorを追加すると下記のようになる。
```python
class Taiyaki: #クラスを定義

    def __init__(self,name): #Constructorを定義
        self.name = name
    
    def get_price(self,price,tax): #メソッドを定義
        print(price*tax)

t_1 = Taiyaki("shibuya") #インスタンス化
print(t_1.name)
# > shibuya
```
前述の通り、Constructorはインスタンス化と同時に自動で呼び出されるため、コンストラクタ実行に引数が必要な場合はインスタンス自体にその引数を設定する必要がある（`Taiyaki("shibuya")`）。

## 4. Finalizerの実装
Finalizerとは、メソットの一種であり、`__del__()`というメソッド名で定義する。このメソッドはインスタンスを削除するためのメソッドである。
```python
class Taiyaki: #クラスを定義

    def __init__(self,name): #Constructorを定義
        self.name = name
    
    def get_price(self,price,tax): #メソッドを定義
        print(price*tax)
    def __del__(self): #Finalizerを定義
        print("---called del---")

t_1 = Taiyaki("shibuya") #インスタンス化
del t_1 #Finalizer実行
# > ---called del---
```

## 5. 継承
継承とは、あるクラスの機能を受け継ぎながら、新しいクラスを定義することである。ここではそれぞれ親クラス、子クラスと呼ぶが、呼び方は色々ある。
||呼び方|
|:-:|:-:|
|継承元のクラス|親クラス、基底クラス、スーパークラス|
|継承先のクラス|子クラス、派生クラス、サブクラス|

これによって、既存のクラスと類似しているが僅かに異なる機能を持ったクラスを作成したいときに便利である。
例えば、`Taiyaki`クラスを親クラスとして、`Ohbanyaki`クラスという子クラスを作成してみよう。
```python
class Taiyaki: #クラスを定義

    def __init__(self,name): #Constructorを定義
        self.name = name
    
    def get_price(self,price,tax): #メソッドを定義
        print(price*tax)

class Ohbanyaki(Taiyaki): #Taiyakiクラスの継承
    def get_area(self):
        print(3.14*3**2)

    
t_1 = Taiyaki("shibuya") #インスタンス化
o_1 = Ohbanyaki("shibuya")
print(o_1.get_price(200,1.08)) #親クラスのメソッドを使用
print(o_1.get_area()) #子クラスのメソッドを使用
```

### メソッドのオーバーライド
親クラスのメソッドと同じ名前のメソッドを子クラスで定義した場合、「メソッドのオーバーライドをする」と表現する。
例を示した上で挙動について説明する。
```python
class Taiyaki: #クラスを定義
    def __init__(self,name,area): #Constructorを定義
        self.name = name
        self.area = area
    
    def get_price(self,price,tax): #メソッドを定義
        print(price*tax)

class Ohbanyaki(Taiyaki): #Taiyakiクラスの継承
    def __init__(self, name, radius) #メソッドのオーバーライド
        self.name = name
        self.radius = radius
    def get_area(self):
        print(3.14*self.radius**2)

o_1 = Ohbanyaki("shibuya",3) #インスタンス化
o_1.get_area()
# > 28.26
```
この場合、子クラス`Ohbanyaki`のインスタンスを作成したときに、親クラスの`__init__()`ではなく、オーバーライドした子クラスの`__init__()`が参照される。

:::note warn
注意
オーバーライドされた親クラスのメソッドは、子クラスのインスタンスでは一切使えない。
オーバーライド元のメソッド内容を継承したいときはsuper関数を用いる。
```python
class Taiyaki: #クラスを定義
    def __init__(self,name,area): #Constructorを定義
        self.name = name
        self.area = area
        
class Ohbanyaki(Taiyaki): #Taiyakiクラスの継承
    def __init__(self, name, area) #メソッドのオーバーライド
        super().__init__(name,area)
        print(self.name)
        print(self.area)
```
:::

## 6. クラスメソッド
クラスメソッドとは、インスタンスを作成しなくても実行できるクラス内のメソッドのことである。
例を示した上で挙動について説明する。
```python
class Taiyaki: #クラスを定義

    feature = "oishii!!"

    @classmethod #以下のメソッドをクラスメソッドに変更するデコレータ
    def add_feature(cls): #クラスメソッドを定義
        cls.feature += "_taiyaki!!"
        return cls.feature

print(Taiyaki.add_feature())
# > oishii!!_taiyaki!!

t_1 = Taiyaki()
print(t_1.add_feature()) #インスタンスからクラスメソッドを呼び出すことも可能
# > oishii!!_taiyaki!!
```
メソッド作成時にインスタンス自身の`self`を引数に記述したのと同様に、クラスメソッド作成時にはクラス自身を表す`cls`を引数に記述する必要がある。
また、インスタンスからクラスメソッドを呼び出すことも可能である。クラスメソッドはクラスに対する処理であるため、どのようなインスタンスから呼び出しても同じ結果が得られる。

:::note
クラスメソッドはデコレータの一種である。
:::

## 7. スタティックメソッド
スタティックメソッドは、クラスメソッドと同じくインスタンスを作成しなくても実行できるメソッドである。クラスメソッドと異なる点として、クラス自身を表す`cls`引数やインスタンス自身を表す`self`を引数を受け取らないことである。つまり、実質的な動作内容としては関数と変わらない。
下記に実装例を示す。
```python
class Taiyaki: #クラスを定義
    def __init__(self,name,area): #Constructorを定義
        self.name = name
        self.area = area
        
    @staticmethod #以下のメソッドをスタティックメソッドに変更するデコレータ
    def multi(x,y): #スタティックメソッドを定義
        return x*y

    @staticmethod #以下のメソッドをスタティックメソッドに変更するデコレータ
    def call_name(): #スタティックメソッドを定義
        print(Taiyaki.name)

print(Taiyaki.multi(3*4))
# > 12
```
このように、スタティックメソッドの引数には`self`と`cls`を入れない。そのため、もしスタティックスタック内でクラス変数を使用したい場合は　`Taiyaki.name`のようにクラス名から呼び出す必要がある。

:::note
スタティックメソッドはデコレータの一種である。
:::

# 参照
[Python 3.12.2 documentation](https://docs.python.org/3/ "python公式HPより")

[【徹底解説】Pythonのクラスの基本からクラス継承やクラス変数などまでわかりやすく｜クラスの使い方も解説【Python入門・応用21】](https://www.youtube.com/watch?v=Q5eY2I46qmA "YouTube - キノコード / プログラミング学習チャンネル 様より")

[【脱オブジェクト指向初心者！】Pythonの継承の基礎を学ぼう](https://www.sejuku.net/blog/68040 "SAMURAI ENGINEER Blog 様より")