クラスとオブジェクト

ここまでのいくつかのレッスンでは、関数を使ってコードを整理する——「何をするか」に焦点を当てた「手続き型」アプローチをとってきました。オブジェクト指向プログラミング(OOP)は「誰がするか」に焦点を当て、データとそのデータを操作するメソッドをオブジェクトにパッケージ化します。Python のすべてはオブジェクトです。OOP を理解することは重要な前進です。


1. クラスとオブジェクトとは

クラスは設計図であり、オブジェクトはその設計図から作成された具体的なインスタンスです。

PYTHON
# 「Dog」クラスを定義 — 設計図
class Dog:
    pass

# 設計図から具体的な「Dog」オブジェクトを作成
my_dog = Dog()
print(type(my_dog))         # <class '__main__.Dog'>
print(my_dog)               # <__main__.Dog object at 0x...>

「犬」という品種が概念(クラス)であるのと同様に、あなたが飼っている特定の犬のワンちゃんはインスタンス(オブジェクト)です。1 つのクラスから無数のオブジェクトを作成できます。

💡 現実世界の例え: クラスは「設計図」、オブジェクトは「設計図から作られた実体」です。1 つの設計図から多くの実体を製造でき、それぞれが独自の状態を持ちます(例:異なる犬は異なる名前と年齢を持ちます)。


2. __init__:コンストラクタメソッド

オブジェクトを作成するとき、__init__ メソッドが自動的に呼び出され、オブジェクトの属性を初期化します:

PYTHON
class Dog:
    """Dog class"""
    def __init__(self, name, age):
        self.name = name    # インスタンス属性
        self.age = age

# オブジェクト作成時にパラメータを渡す
dog1 = Dog("Wangcai", 3)
dog2 = Dog("Xiaobai", 1)

print(dog1.name)            # Wangcai
print(dog2.name)            # Xiaobai
print(dog1.age)             # 3
⚠️ 補足: __init__ はコンストラクタではありません——オブジェクトはそれが呼び出される前に作成されます。実際のコンストラクタは __new__ ですが、それに触れる必要はほとんどありません。__init__ の最初のパラメータは常に self で、現在のオブジェクトインスタンスを表します。

例:Student クラスの定義(難易度 ⭐)

PYTHON
class Student:
    """Student class"""
    def __init__(self, name, student_id, score):
        self.name = name
        self.student_id = student_id
        self.score = score

    def introduce(self):
        """自己紹介"""
        return f"My name is {self.name}, ID: {self.student_id}, score: {self.score}."
    
    def is_pass(self):
        """合格かどうかを確認"""
        return self.score >= 60

# 学生オブジェクトを作成
s1 = Student("Zhang San", "2024001", 85)
s2 = Student("Li Si", "2024002", 45)

print(s1.introduce())       # My name is Zhang San, ID: 2024001, score: 85.
print(s2.introduce())       # My name is Li Si, ID: 2024002, score: 45.
print(f"Is Zhang San passing? {s1.is_pass()}")   # True
print(f"Is Li Si passing? {s2.is_pass()}")   # False
▶ 試してみよう

3. メソッド:オブジェクトの振る舞い

クラス内で定義された関数をメソッドと呼びます。最初のパラメータは常に self で、メソッドを呼び出すオブジェクト自体を表します。

PYTHON
class Calculator:
    """簡易電卓"""
    def __init__(self):
        self.result = 0
    
    def add(self, value):
        self.result += value
        return self
    
    def subtract(self, value):
        self.result -= value
        return self
    
    def show(self):
        return self.result

calc = Calculator()
calc.add(10)
calc.subtract(3)
print(calc.show())                  # 7

# メソッドチェーン — 各メソッドが self を返す
result = Calculator().add(10).subtract(3).add(5).show()
print(result)                       # 12
💡 メソッドチェーンのコツ: self を返すメソッドはチェーンできます。多くの Python ライブラリ(pandas など)がこのパターンを広く使用しています。


4. インスタンス属性 vs クラス属性

属性はインスタンス(各オブジェクトに固有)またはクラス(すべてのオブジェクトで共有)に定義できます:

PYTHON
class Student:
    # クラス属性 — すべての学生で共有
    school = "First High School"
    total_count = 0
    
    def __init__(self, name):
        # インスタンス属性 — 各学生に固有
        self.name = name
        Student.total_count += 1

s1 = Student("Zhang San")
s2 = Student("Li Si")

# クラス属性はクラス名でアクセス
print(Student.school)       # First High School
print(Student.total_count)  # 2

# インスタンスからもアクセス可能(同名のインスタンス属性がない場合)
print(s1.school)            # First High School
print(s2.school)            # First High School

# クラス属性の変更はすべてのインスタンスに影響
Student.school = "Second High School"
print(s1.school)            # Second High School

例:銀行口座(難易度 ⭐⭐)

PYTHON
class BankAccount:
    """銀行口座"""
    bank_name = "Python Bank"          # クラス属性
    interest_rate = 0.003              # 年利 0.3%
    
    def __init__(self, owner, balance=0):
        self.owner = owner            # インスタンス属性
        self.balance = balance        # インスタンス属性
    
    def deposit(self, amount):
        self.balance += amount
        return f"Deposited {amount}, balance: {self.balance}"
    
    def withdraw(self, amount):
        if amount > self.balance:
            return "Insufficient balance!"
        self.balance -= amount
        return f"Withdrew {amount}, balance: {self.balance}"
    
    def yearly_interest(self):
        """年間利息を計算"""
        interest = self.balance * BankAccount.interest_rate
        return f"Yearly interest: {interest:.2f}"

acc1 = BankAccount("Zhang San", 10000)
acc2 = BankAccount("Li Si", 5000)

print(acc1.deposit(5000))           # Deposited 5000, balance: 15000
print(acc2.withdraw(2000))          # Withdrew 2000, balance: 3000
print(acc1.yearly_interest())       # Yearly interest: 4.50

# すべての口座が同じ金利を共有
print(acc1.interest_rate)           # 0.003
print(acc2.interest_rate)           # 0.003
▶ 試してみよう

よくあるユースケース


❓ よくある質問

Q メソッドと関数の違いは何ですか?
A メソッドはクラス内で定義された関数で、self を最初のパラメータとして持ちます。関数は独立して存在できます。メソッドはオブジェクトまたはクラスを介して呼び出す必要があります。本質的に、メソッドは Python が呼び出し元を最初の引数として自動的に渡す関数です。
Q self を他の名前に変更できますか?
A 技術的には可能ですが、決して変更しないでください。 self は Python コミュニティの固いルールです。名前を変更しても機能はしますが、コードを読む人(将来の自分を含む)が違和感を持ちます。常に self を使用してください。
Q クラスと関数はいつ使い分ければよいですか?
A 状態(データ)を維持する必要がある場合はクラスを使います——残高を追跡する銀行口座など。1 回限りの計算(入力 → 処理 → 出力)の場合は関数を使います——温度変換など。簡単なルール:1~2 個の関数で十分なら関数で済ませ、関連する複数の関数が同じデータを共有する場合はクラスを使います。

📖 まとめ


📝 練習問題

  1. 初級(難易度 ⭐)Car クラスを定義し、brandcolorspeed 属性を持たせます。速度を上げる accelerate(value) メソッドと現在の速度を表示する show_speed() メソッドを含めてください。

  2. 中級(難易度 ⭐⭐)Student クラスを定義します。クラス属性で学生の総数を追跡し、インスタンス属性で各学生の名前とスコアを記録します。英字グレード(A、B、C、D)を返す get_grade() メソッドを実装してください。

  3. 上級(難易度 ⭐⭐⭐)Library クラスと Book クラスを定義します。Book はタイトル、著者、貸出状態を持ちます。Library は 4 つのメソッド add_book(book)borrow(title)return_book(title)show_books() を持ちます。

100%