Pythonオブジェクト指向入門 🚀

Step 1: オブジェクト指向って何? 🤔 - プログラミングの新しい視点

導入:これまでのプログラミングと何が違うの?

オブジェクト指向の基本的な考え方:モノ(オブジェクト)で考える


Step 2: クラスとインスタンス 🛠️ - 設計図と実体

クラス:オブジェクトの設計図

# 例: 犬クラスの設計図(まだ中身は空っぽ)
class Dog:
    pass # pass は「何もしない」という意味。今は空っぽでOK。

インスタンス:設計図から作られた実物

# Dogクラス(設計図)からインスタンス(具体的な犬)を作成
pochi = Dog() # 「ポチ」という名前の犬インスタンスができた(まだ名前は設定できてないけど)
hachi = Dog() # 「ハチ」という名前の犬インスタンスができた

__init__メソッド(コンストラクタ):インスタンス誕生の儀式

self って何? なぜ必要なの?

class Dog:
    # __init__ はインスタンスが作られるときに自動で呼ばれる
    def __init__(self, name_param, age_param): # name_param, age_param は外から渡される値
        print(f"Dogの__init__が呼ばれました! {name_param}を作ります。")
        self.name = name_param  # self(インスタンス自身)のname属性に、渡されたname_paramを設定
        self.age = age_param    # self(インスタンス自身)のage属性に、渡されたage_paramを設定

# インスタンス作成時に名前と年齢を指定
# このとき、Dogクラスの __init__ メソッドが自動的に呼ばれる
# "ポチ" が name_param に、3 が age_param に渡される
# そして、作られるインスタンス自身が self に渡される
pochi = Dog("ポチ", 3)
print(f"{pochi.name}は{pochi.age}歳です。") # 出力: ポチは3歳です。

hachi = Dog("ハチ", 5)
print(f"{hachi.name}は{hachi.age}歳です。") # 出力: ハチは5歳です。

Step 3: 属性とメソッド 🔧 - モノの状態と操作

属性(アトリビュート):モノが持つデータ

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

pochi = Dog("ポチ", 3)

# 属性にアクセスして値を表示
print(pochi.name)  # 出力: ポチ
print(pochi.age)   # 出力: 3

# 属性の値を変更
pochi.age = 4
print(f"{pochi.name}は{pochi.age}歳になりました。") # 出力: ポチは4歳になりました。

メソッド:モノができる操作(振る舞い)

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 吠えるメソッド
    # このメソッドを呼び出した犬インスタンスが self に入る
    def bark(self):
        # self.name を使って、その犬自身の名前で吠える
        print(f"{self.name}がワン!と吠えました。")

    # 歳をとるメソッド
    def get_older(self, years=1): # yearsはオプション引数、デフォルトは1
        self.age += years # self.age(その犬自身の年齢)にyearsを加える
        print(f"{self.name}は{self.age}歳になりました。")

pochi = Dog("ポチ", 3)

# pochiインスタンスのbarkメソッドを呼び出す
# このとき、pochi自身がbarkメソッドのselfに渡される
pochi.bark()  # 出力: ポチがワン!と吠えました。

pochi.get_older() # 出力: ポチは4歳になりました。
pochi.get_older(2) # 出力: ポチは6歳になりました。

Step 4: オブジェクト指向の三大要素 🌟 - より高度な設計へ

オブジェクト指向には、「カプセル化」「継承」「ポリモーフィズム(多態性)」という、プログラムをより良く設計するための重要な考え方があります。

1. カプセル化:情報を守り、整理する

class Player:
    def __init__(self, name, hp):
        self.name = name
        self.__hp = hp # アンダースコア2つで開始。外から直接触られたくないHP。
        # 初期HPが不正な値でないかチェックすることもできる
        if self.__hp < 0:
            self.__hp = 0

    # HPを取得するためのゲッターメソッド
    def get_hp(self):
        return self.__hp

    # HPを設定するためのセッターメソッド
    def set_hp(self, new_hp):
        if new_hp < 0:
            self.__hp = 0 # HPは0未満にはしない
            print(f"{self.name}のHPは0より小さくできません。HPを0に設定しました。")
        elif new_hp > 100: # 例えば最大HPが100だとして
            self.__hp = 100
            print(f"{self.name}のHPは100より大きくできません。HPを100に設定しました。")
        else:
            self.__hp = new_hp
        print(f"{self.name}のHPは{self.__hp}になりました。")

    def take_damage(self, damage):
        print(f"{self.name}は{damage}のダメージを受けた!")
        self.set_hp(self.__hp - damage) # HP変更はセッター経由で行う

# 例
player1 = Player("勇者", 50)
print(f"{player1.name}の現在のHP: {player1.get_hp()}") # 出力: 勇者の現在のHP: 50

# 直接 __hp を変更しようとしても、うまくいかないか、意図しないことになる
# player1.__hp = -100 # これは避けるべき!
# print(player1.__hp) # 意図した通りにアクセスできない(名前マングリングのため)

player1.set_hp(80)    # 出力: 勇者のHPは80になりました。
player1.set_hp(120)   # 出力: 勇者のHPは100より大きくできません。HPを100に設定しました。
player1.take_damage(150) # 出力: 勇者は150のダメージを受けた!
                         # 出力: 勇者のHPは0より小さくできません。HPを0に設定しました。

2. 継承:性質を引き継ぎ、拡張する

class Animal:  # 親クラス
    def __init__(self, name):
        print("Animalの__init__が呼ばれました")
        self.name = name

    def eat(self):
        print(f"{self.name}が食事をしています。")

# Animalクラスを継承してDogクラスを作る
# class 子クラス名(親クラス名):
class Dog(Animal):  # Animalクラスを継承する子クラス
    def __init__(self, name, breed): # Dog独自の初期化
        print("Dogの__init__が呼ばれました")
        # まず親クラスの__init__を呼び出して、Animalとしての初期設定をしてもらう
        super().__init__(name) # これで self.name = name が実行される
        self.breed = breed     # Dog独自の属性

    def bark(self): # Dogクラス独自のメソッド
        print(f"{self.name}({self.breed})がワン!と吠えました。")

    # 親クラスのeatメソッドをオーバーライド(上書き)
    def eat(self):
        super().eat() # 親クラスのeatメソッドも呼び出したい場合
        print(f"{self.name}はドッグフードが大好きです。")

class Cat(Animal): # Animalクラスを継承する子クラス
    def __init__(self, name, favorite_toy):
        print("Catの__init__が呼ばれました")
        super().__init__(name)
        self.favorite_toy = favorite_toy

    def meow(self):
        print(f"{self.name}がニャー(おもちゃは{self.favorite_toy})と鳴きました。")

    # Catもeatメソッドをオーバーライド
    def eat(self):
        # super().eat() # 猫は親の食べ方をしないことにする
        print(f"{self.name}は魚をカリカリ食べています。")


# 例
my_dog = Dog("ポチ", "柴犬")
my_dog.eat()    # Dogのeatが呼ばれる -> Animalのeatも呼ばれ、その後Dog独自のメッセージ
                # 出力: Animalの__init__が呼ばれました
                # 出力: Dogの__init__が呼ばれました
                # 出力: ポチが食事をしています。
                # 出力: ポチはドッグフードが大好きです。
my_dog.bark()   # 出力: ポチ(柴犬)がワン!と吠えました。

my_cat = Cat("タマ", "ねこじゃらし")
my_cat.eat()    # Catのeatが呼ばれる
                # 出力: Animalの__init__が呼ばれました
                # 出力: Catの__init__が呼ばれました
                # 出力: タマは魚をカリカリ食べています。
my_cat.meow()   # 出力: タマがニャー(おもちゃはねこじゃらし)と鳴きました。

3. ポリモーフィズム(多態性):同じ指示で、異なる振る舞い

# 前のAnimal, Dog, Catクラスの定義があるとして

# いろんな動物を一つのリストにまとめて入れる
animals = [
    Dog("レックス", "ラブラドール"), # Dogインスタンス
    Cat("ミケ", "毛糸玉"),         # Catインスタンス
    Dog("マックス", "チワワ")      # またDogインスタンス
]

print("\n--- みんなで食事の時間 ---")
# animalsリストの中身を一つずつ取り出してanimalという変数に入れる
for animal in animals:
    # animal が Dogインスタンスのときは Dogのeat() が呼ばれる
    # animal が Catインスタンスのときは Catのeat() が呼ばれる
    # 同じ animal.eat() という呼び出し方なのに、動きが変わる!これがポリモーフィズム。
    animal.eat()
    # もし、animal.bark() と書くと、animalがCatのときにエラーになる
    # (Catクラスにはbarkメソッドがないため)。
    # ポリモーフィズムは、共通のインターフェース(ここではeatメソッド)に対して機能する。

# --- 共通のインターフェースを意識する ---
# 例えば、全てのAnimalが「鳴く」という共通の動作を持つように設計してみましょう。
# (これは上記のクラス定義を少し変更するイメージです)

class Animal: # 再定義 (speakメソッド追加のイメージ)
    def __init__(self, name):
        self.name = name
    def eat(self):
        print(f"{self.name}が何かを食べています。")
    def speak(self): # 親クラスに共通のメソッドを用意
        print(f"{self.name}が鳴きました。")

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed
    def eat(self):
        super().eat()
        print(f"{self.name}はドッグフードを食べています。")
    def speak(self): # 子クラスでオーバーライド
        print(f"{self.name}({self.breed})がワンワン!")

class Cat(Animal):
    def __init__(self, name, favorite_toy):
        super().__init__(name)
        self.favorite_toy = favorite_toy
    def eat(self):
        print(f"{self.name}は魚を食べています。")
    def speak(self): # 子クラスでオーバーライド
        print(f"{self.name}がニャー!お気に入りは{self.favorite_toy}。")

# 新しいインスタンスで試す
animals_v2 = [
    Dog("バディ", "ゴールデンレトリバー"),
    Cat("ルナ", "レーザーポインター")
]

print("\n--- みんなで鳴いてみよう ---")
for pet in animals_v2:
    pet.speak() # Dogなら犬の鳴き方、Catなら猫の鳴き方が実行される

Step 5: 実践と応用 🚀 - 学んだことを使ってみよう!

簡単なプログラムの設計と実装

# 例:簡単なRPGキャラクター

class Character:
    def __init__(self, name, hp, attack_power):
        self.name = name
        self._hp = hp # _hp は「直接触らないでね」という意図のHP
        self._max_hp = hp # 最大HPも保持しておく
        self.attack_power = attack_power

    def get_hp(self):
        pass # 省略

    def _set_hp(self, new_hp): # 内部でのみ使うHP設定メソッド(簡易的なカプセル化)
        pass # 省略

    def attack(self, target): # target も Character のインスタンスを期待
        pass # 省略

    def take_damage(self, damage):
        pass # 省略

    def status(self):
        pass # 省略

class Hero(Character): # Characterクラスを継承
    def __init__(self, name, hp, attack_power, magic_power):
        super().__init__(name, hp, attack_power) # 親クラスの__init__を呼び出す
        self.magic_power = magic_power

    def cast_spell(self, target):
        pass # 省略

    # Hero独自のattackメソッド(ポリモーフィズムの例として、少し振る舞いを変える)
    def attack(self, target):
        pass # 省略

class Monster(Character): # Characterクラスを継承
    def __init__(self, name, hp, attack_power, drop_item):
        pass # 省略

    def roar(self):
        pass # 省略

# --- 実行例 ---
hero = Hero("アベル", 100, 15, 10)
slime = Monster("スライムキング", 80, 10, "キングゼリー")
goblin = Monster("ゴブリン", 50, 8, "汚れた布切れ")

characters = [hero, slime, goblin]

for char in characters:
    char.status()

print("\n--- バトル開始! ---")
hero.attack(slime) # Heroのattackが呼ばれる
if slime.get_hp() > 0:
    slime.attack(hero) # Monster (Character) のattackが呼ばれる

hero.cast_spell(goblin)
if goblin.get_hp() > 0:
    goblin.roar()

print("\n--- バトル終了後のステータス ---")
for char in characters:
    char.status()

Pythonの組み込み型やライブラリにおけるオブジェクト指向

my_string = "hello python"
# my_string は文字列(str)クラスのインスタンス(オブジェクト)
print(type(my_string)) # <class 'str'> と表示される

# .upper() はstrオブジェクトが持っているメソッド
upper_string = my_string.upper()
print(upper_string)  # HELLO PYTHON

my_list = [1, 2, 3]
# my_list はリスト(list)クラスのインスタンス(オブジェクト)
print(type(my_list)) # <class 'list'> と表示される

# .append() はlistオブジェクトが持っているメソッド
my_list.append(4)
print(my_list) # [1, 2, 3, 4]

これであなたもオブジェクト指向プログラマーの仲間入りです!この考え方を身につけて、より複雑で、より整理されていて、より面白いプログラム作りに挑戦してみてください! 🎉