Pythonクラス入門:学生時代の脳筋コードを卒業するための再学習メモ


# 背景
学生時代に学んでなんとなくは理解していたけど、会社に入って使っていると、ちゃんと説明できなかったり実装できなかったりする技術があることに徐々に気づいていきました。
本記事シリーズでは自分の知識・スキルを定着することと、同じように悩んでいる人の助けになることを目的に記事を書いていきます。


# 本記事について
- 本記事では、Pythonの「クラス」「継承」「オーバーライド」などの基本概念を整理します。
- クラスに関する基礎概念を整理して、Pythonで簡易な実装ができるようになることが本記事のゴールです。


# 私がクラスに苦手意識を感じていた理由
クラスを苦手意識を持っていた理由は、主に以下の2点に集約されると思います。

- **使い所がわからない**
- **文法がわからない**
  
プログラミングを始めたばかりの頃は、プログラミング全体の基本的な概念に慣れること自体が精一杯でした。そんな中で「クラス」という、さらに抽象度の高い概念に触れたことで、完全にパニックに陥ってしまいました。
**「クラスとは設計図、インスタンスとはその実体」** という説明は何度も目にしましたが、その本質的な意義を理解しないまま、ただ暗記するだけで終わっていたように思います。さらに、self など、それまで習ってきた直感的な関数には見られない特殊な文法が出てきたことで、ますます混乱しました。加えて、私が関わっていた研究の中では、**クラスを使わずとも関数ベースで処理を組み立てることで十分に対応できていた**ため、クラスに対する苦手意識を抱えたまま、改善の機会がないまま何年も過ごしてしまいました。
この記事では、**「なぜクラスが必要なのか」「どんな場面でクラスが有効なのか」**について、関数を用いた実装と比較しながら、できるだけわかりやすく解説していきたいと思います。



# なぜ関数ではなくクラスを使うのか？
## 関数だけで管理する例

```python
# 料理ごとに個別の変数と関数で管理
dish1_name = "担々麺"
dish1_region = "四川"
dish1_spicy = 4

def describe_dish(name, region, spicy_level):
    print(f"{name}は{region}地方の料理で、辛さレベルは{spicy_level}です。")

describe_dish(dish1_name, dish1_region, dish1_spicy)
```
### 問題点:
- 料理が10種類、100種類と増えていくと変数名・関数の管理が煩雑になる
- データと操作がバラバラに存在するので、関連性が見えにくい
- 共通処理が増えると、コピペや重複コードが発生しやすくなる

## クラスを使うとどうなるか？

```python
class ChineseDish:
    def __init__(self, name, region, spicy_level):
        self.name = name
        self.region = region
        self.spicy_level = spicy_level
    
    def describe(self):
        print(f"{self.name}は{self.region}地方の中国料理で、辛さレベルは{self.spicy_level}です。")

dish1 = ChineseDish("担々麺", "四川", 4)
dish2 = ChineseDish("麻婆豆腐", "四川", 5)

dish1.describe()
dish2.describe()
```

### クラスの強み：
- データ（属性）と操作（メソッド）を1つにまとめられる
- インスタンスをリストや辞書に格納し、一括で操作できる
- 似た料理をまとめた「四川料理クラス」など、より構造化できる


---

# クラスに関連した主要な知識
## クラスとインスタンス
プログラムを書く上で、しばしば「クラスは設計図、インスタンスはその設計図から作られた実物」と説明されます。最初は抽象的に感じますが、身近な例で捉えると理解が進みます。
以下では、「中国料理（ChineseDish）」というクラスを定義します。中国料理は地域ごとに味や辛さが異なり、それらを属性としてまとめて管理したいときにクラスはとても便利です。


```python
class ChineseDish:
  cuisine_type = "中国料理"
  def __init__(self, name, region, spicy_level):
    self.name = name
    self.region = region
    self.spicy_level = spicy_level
  def describe(self):
    print(f"{self.name}は{self.cuisine_type}の代表的な料理です。")
    print(f"発祥地: {self.region}")
    print(f"辛さレベル: {self.spicy_level}")

```

## インスタンス化とコンストラクタ
Pythonでは、クラスを使って具体的なオブジェクト（インスタンス）を作成します。このときに呼び出されるのが「コンストラクタ」と呼ばれる`__init__` メソッドです。

- 目的：インスタンス生成時に「共通の初期設定」を行います。
- 例：料理名・地域・辛さなどを最初に設定したい
  - ゲームやアプリで、「キャラ生成」「会員登録」「初期設定」が必要な場面で必須。


```python
dish = ChineseDish("担々麺","四川","4")
dish.describe()
```
### 出力例
```
担々麺は中国料理の代表的な料理です。
発祥地: 四川
辛さレベル: 4
```

## クラスを活用する理由 - 継承 -
関数ベースのコードでは、似た処理を何度も書く必要が出てきますが、クラスを使えば「共通部分を親クラスにまとめて、差分だけを子クラスに書く」といった設計ができます。これを継承と呼びます。Pythonでは`class 子クラス（親クラス）: `という構文で継承を行います。

- 目的：共通機能を親にまとめ、子クラスで「差分だけ」を書けます。
- 例：ChineseDish（親） → SichuanDish（子）で辛さデフォルト4

```python
class SichuanDish(ChineseDish):
  def __init__(self,name,region):
    super().__init__(name,region,spicy_level=4)
```
## メソッドの上書き（オーバーライド）

子クラスで親クラスのメソッドを再定義すると、振る舞いをカスタマイズできます。これをオーバーライドといいます。
- 目的：子クラスで振る舞いを上書きし、カスタマイズ可能にします。
- 例：
  - 四川料理だけ「激辛注意！」と表示したいとき、親の describe() をオーバーライドします
  - ゲームで「ボスキャラだけ特殊な攻撃」みたいな処理に応用できます



```python
class SichuanDish(ChineseDish):
    def describe(self):
        print(f"{self.name} は四川料理（激辛！）です。")
        super().describe()
```



## 継承を制限するには？
すべてのクラスが継承されるべきとは限りません。Pythonでは、@final デコレータや abc モジュールを使って継承やオーバーライドを制限できます。

```python
from typing import final

@final
class Base:
  pass

# 以下はType Chekerから警告が出る。
# class Derived(Base): 
#   pass
```


## カプセル化とは
クラスの中のデータを外部から勝手に変更されないようにする設計思想をカプセル化と呼びます。Pythonでは、アンダースコアを用いて「これは外から直接触るべきではないよ」と示すのが一般的です。

- 目的：勝手に値を変更されないようにして、安全な操作だけを許します。
- 例：
  - 外部から .spicy_level = "超辛" みたいな不正入力を防技ます。
  - GUIやWeb APIでの入力チェック、セキュリティが必要な場面で重要です。


```python
class ChineseDish:
    def __init__(self, name, region, spicy_level):
        self.name = name
        self.region = region
        self._spicy_level = spicy_level

    def get_spicy_level(self):
        return self._spicy_level
```


## 多態性とは
多態性（ポリモーフィズム）とは、「親クラス型の変数で、子クラスの振る舞いを扱える」柔軟な仕組みです。共通の型でまとめられるので、コードの再利用性が高まります。
- 目的：親クラスのリストに子クラスを混ぜて一括処理できます。
- 効果：複雑なゲームや業務アプリでも、種類ごとの処理を統一的に書けます。

```python
dishes = [
    ChineseDish("饺子", "中国", 1),
    SichuanDish("四川麻婆豆腐", "四川")
]

for d in dishes:
    d.describe()  

```


## super()の使い方
`super()`は、親クラスのメソッドを呼び出す際に使います。オーバーライドした際に、元の動作を活かしたいときに便利です。
- 目的：オーバーライドしつつ、元の処理を生かします。
- 例：
  - 四川料理のdescribe()で、まず「激辛です！」と警告してから、親の詳細表示も続ける
  - 親の設定＋子での拡張という「組み立て」に便利

```python
class SpicyDish(ChineseDish):
    def __init__(self, name, region):
        super().__init__(name, region, spicy_level=5)
        print("これは超激辛料理です！")
```


## is-a 関係とは？
「あるクラスが、別のクラスの一種である」と言える関係をis-a関係と呼びます。継承関係の設計において重要です。
例：`SichuanFood`は`ChineseFood`の一種である。`Sichuan food is a Chinese food.`.
- 目的：継承が「正しいモデリング」かを考える軸です。
- 例：
  - SichuanDish is-a ChineseDish → OK
  - DeliveryService is-a ChineseDish → ？？（関係が変）
- 効果：「継承すべきか、別の構成にすべきか」を判断する基準になります。



## まとめ：クラスは「増える・複雑になる」前提の設計
関数だけで書ける処理も、データが増え、機能が複雑になると破綻します。
クラスを使えば、
- 共通の初期設定
- データと操作のまとまり
- 派生系の追加・変更
- 関数の上書き・再利用
が自然にでき、結果としてコードが読みやすく・保守しやすくなります。

## 補足：
アンダースコアの使い方
  - `_name`: 内部利用の慣習的表記
  - `__name`: 名前の衝突を防ぐための名前修飾
  - `__init__`; Pythonが自動で呼び出す特殊メソッド（マジックメソ

- selfとは？
  - selfはインスタンス自身を指します。クラス内で変数やメソッドにアクセスするために必要です。


# 参考文献
『スッキリわかるJava入門（第3部）』をベースに、Python向けにアレンジして解説しました。