多態性とマジックメソッド

前の 2 つのレッスンでは、クラスと継承の「形式」を学びました。このレッスンでは「精神」を学びます。多態性により、異なるクラスのオブジェクトを同じ方法で呼び出せます——具体的なクラスを知る必要はなく、必要なメソッドを持っていることだけが重要です。マジックメソッドにより、カスタムクラスが組み込み型と同じように自然に振る舞えるようになります。


1. 多態性とは

多態性(ポリモーフィズム)は「多くの形態」を意味します——同じインターフェース、異なる実装:

PYTHON
class Dog:
    def speak(self):
        return "Woof!"

class Cat:
    def speak(self):
        return "Meow!"

class Duck:
    def speak(self):
        return "Quack!"

# 多態性:クラスに関係なく、speak() メソッドがあればよい
animals = [Dog(), Cat(), Duck()]
for animal in animals:
    print(animal.speak())

出力:

TEXT
Woof!
Meow!
Quack!
💡 ダックタイピング: 「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルです。」Python はオブジェクトの型を気にしません——必要なメソッドを持っているかどうかだけが重要です。これは Python の最も柔軟な設計の 1 つです。

例:図形の面積計算(難易度 ⭐⭐)

PYTHON
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

class Circle:
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14159 * self.radius ** 2

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height
    
    def area(self):
        return 0.5 * self.base * self.height

# 多態性 — 図形に関係なく、面積を計算するだけ
shapes = [Rectangle(3, 4), Circle(5), Triangle(6, 8)]
for shape in shapes:
    print(f"Area: {shape.area():.2f}")
▶ 試してみよう

出力:

TEXT
Area: 12.00
Area: 78.54
Area: 24.00

2. マジックメソッド:クラスをより「Python らしく」

マジックメソッドは、ダブルアンダースコアのプレフィックスとサフィックスを持つ特別なメソッドです——__init____str____len__ など。カスタムクラスが組み込み型のように振る舞えるようにします。

__str____repr__:より親しみやすい出力

PYTHON
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        """ユーザーフレンドリーな文字列表現"""
        return f"{self.name} ({self.age} years old)"
    
    def __repr__(self):
        """開発者フレンドリーな文字列表現"""
        return f"Person('{self.name}', {self.age})"

p = Person("Zhang San", 25)
print(p)                    # Zhang San (25 years old) — __str__ を呼び出し
print(repr(p))              # Person('Zhang San', 25) — __repr__ を呼び出し

__len__:オブジェクトが len() をサポート

PYTHON
class Team:
    def __init__(self, members=None):
        self.members = members or []
    
    def add(self, member):
        self.members.append(member)
    
    def __len__(self):
        return len(self.members)
    
    def __str__(self):
        return f"Team({', '.join(self.members)})"

team = Team()
team.add("Zhang San")
team.add("Li Si")
team.add("Wang Wu")

print(len(team))            # 3 — __len__ を呼び出し
print(team)                 # Team(Zhang San, Li Si, Wang Wu)

__getitem__:オブジェクトがインデックスをサポート

PYTHON
class Playlist:
    def __init__(self):
        self.songs = []
    
    def add(self, song):
        self.songs.append(song)
    
    def __getitem__(self, index):
        return self.songs[index]
    
    def __len__(self):
        return len(self.songs)

playlist = Playlist()
playlist.add("Sunny Day")
playlist.add("Seven Li Fragrance")
playlist.add("Nocturne")

print(playlist[0])          # Sunny Day — __getitem__ を呼び出し
print(len(playlist))        # 3

# スライスと反復をサポート
for song in playlist:
    print(song, end=" ")    # Sunny Day Seven Li Fragrance Nocturne

3. よく使われるマジックメソッド一覧

PYTHON
class Demo:
    def __init__(self, value):      # コンストラクタ
        self.value = value
    
    def __str__(self):              # str() / print()
        return str(self.value)
    
    def __repr__(self):             # repr() / デバッグ
        return f"Demo({self.value})"
    
    def __len__(self):              # len()
        return 1
    
    def __bool__(self):             # bool() / if 条件
        return self.value != 0
    
    def __eq__(self, other):        # ==
        return self.value == other.value
    
    def __lt__(self, other):        # <(ソート用)
        return self.value < other.value
    
    def __add__(self, other):       # + 演算子
        return Demo(self.value + other.value)
    
    def __getitem__(self, key):     # obj[key]
        return key

例:分数クラス(難易度 ⭐⭐⭐)

PYTHON
class Fraction:
    """分数クラス — 加算をサポート"""
    def __init__(self, numerator, denominator=1):
        if denominator == 0:
            raise ValueError("Denominator cannot be zero")
        self.numerator = numerator
        self.denominator = denominator
    
    def __str__(self):
        if self.denominator == 1:
            return str(self.numerator)
        return f"{self.numerator}/{self.denominator}"
    
    def __add__(self, other):
        new_den = self.denominator * other.denominator
        new_num = self.numerator * other.denominator + other.numerator * self.denominator
        return Fraction(new_num, new_den)
    
    def __eq__(self, other):
        return (self.numerator * other.denominator) == (other.numerator * self.denominator)

f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
print(f1)                           # 1/3
print(f1 + f2)                      # 9/18
print(Fraction(1, 2) == Fraction(2, 4))  # True
▶ 試してみよう

4. @dataclass:データクラスの迅速な定義

データを保存するためだけのクラスを書き、__init____repr____eq__ を手動で書くのは面倒です。@dataclass がそれらを自動生成します:

PYTHON
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

@dataclass
class Student:
    name: str
    age: int
    score: float = 0.0    # デフォルト値

# __init__、__repr__、__eq__ が自動生成される
p1 = Point(3.0, 4.0)
p2 = Point(3.0, 4.0)
print(p1)                   # Point(x=3.0, y=4.0)
print(p1 == p2)             # True(すべてのフィールドを自動比較)

s = Student("Zhang San", 20, 85.5)
print(s)                    # Student(name='Zhang San', age=20, score=85.5)
💡 @dataclass を使うタイミング: 1)クラスが主にデータを保存し、メソッドが少ない。2)__init____repr____eq__ などのボイラープレートが必要。3)これらを手動で書きたくない。シンプルなケースでは、手書きのクラスよりもはるかに効率的です。


よくあるユースケース


❓ よくある質問

Q マジックメソッドと通常のメソッドの違いは何ですか?
A マジックメソッドは Python の組み込み操作によって呼び出されます——print()__str__ を、len()__len__ を、==__eq__ を呼び出します。直接呼び出すのではなく、実装するだけで Python が適切なタイミングで自動的に呼び出します。
Q @dataclass と通常のクラスの違いは何ですか?
A @dataclass は書くのに飽き飽きしているメソッドを自動生成します。単なるコードジェネレーターです——生成されたクラスは手書きのものと機能的に同じです。@dataclass でデコレートされたクラスは独自のメソッドも持てます。プロパティを保存するために繰り返し __init__ を書いているなら、@dataclass の出番です。
Q Python は動的型付けです——多態性は自然に実現されますか?
A はい。Python の「ダックタイピング」により、継承なしで多態性が可能です——同じ名前のメソッドを持っているだけで十分です。他の言語(Java/C++)では多態性にインターフェースや抽象クラスが必要です。利点は柔軟性。欠点は型チェックでエラーを発見しにくいことです。

📖 まとめ


📝 練習問題

  1. 初級(難易度 ⭐)Book クラスを定義し、__str____len__(ページ数を返す)を実装。2 冊の本を作成し、print(book)len(book) を出力。

  2. 中級(難易度 ⭐⭐)@dataclass を使って Product クラス(name、price、quantity)を定義。__add__ を実装——同じ名前の 2 つの製品をマージ(数量を合計、価格は平均)。ヒント: @dataclass クラス内で実装。

  3. 上級(難易度 ⭐⭐⭐)Deck クラス(トランプのデッキ)を定義し、__len____getitem__(インデックスとスライスをサポート)、shuffle()random.shuffle を使用)を実装。その後、デッキを作成し、最初の 5 枚のカードを手札として配るコードを書いてください。

100%