多態性とマジックメソッド
前の 2 つのレッスンでは、クラスと継承の「形式」を学びました。このレッスンでは「精神」を学びます。多態性により、異なるクラスのオブジェクトを同じ方法で呼び出せます——具体的なクラスを知る必要はなく、必要なメソッドを持っていることだけが重要です。マジックメソッドにより、カスタムクラスが組み込み型と同じように自然に振る舞えるようになります。
1. 多態性とは
多態性(ポリモーフィズム)は「多くの形態」を意味します——同じインターフェース、異なる実装:
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())
出力:
Woof!
Meow!
Quack!
例:図形の面積計算(難易度 ⭐⭐)
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}")
出力:
Area: 12.00
Area: 78.54
Area: 24.00
2. マジックメソッド:クラスをより「Python らしく」
マジックメソッドは、ダブルアンダースコアのプレフィックスとサフィックスを持つ特別なメソッドです——__init__、__str__、__len__ など。カスタムクラスが組み込み型のように振る舞えるようにします。
__str__ と __repr__:より親しみやすい出力
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() をサポート
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__:オブジェクトがインデックスをサポート
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. よく使われるマジックメソッド一覧
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
例:分数クラス(難易度 ⭐⭐⭐)
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 がそれらを自動生成します:
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)これらを手動で書きたくない。シンプルなケースでは、手書きのクラスよりもはるかに効率的です。
よくあるユースケース
- ダックタイピング:Python の
len()、for...in、sorted()はすべてプロトコルベース——対応するメソッドを実装すれば、あなたのクラスも使えます - データコンテナ:
__getitem__+__len__を実装して、クラスがインデックスと反復をサポート - 演算子オーバーロード:
__add__、__eq__、__lt__でカスタムクラスが+、==、<などをサポート - データクラス:
@dataclassで設定、DTO、値オブジェクトを迅速に定義
❓ よくある質問
print() は __str__ を、len() は __len__ を、== は __eq__ を呼び出します。直接呼び出すのではなく、実装するだけで Python が適切なタイミングで自動的に呼び出します。@dataclass と通常のクラスの違いは何ですか?@dataclass は書くのに飽き飽きしているメソッドを自動生成します。単なるコードジェネレーターです——生成されたクラスは手書きのものと機能的に同じです。@dataclass でデコレートされたクラスは独自のメソッドも持てます。プロパティを保存するために繰り返し __init__ を書いているなら、@dataclass の出番です。📖 まとめ
- 多態性:異なるクラスの、同じ名前のメソッドを持つオブジェクトを同じ方法で呼び出せる
- ダックタイピング:「鳴き声がアヒルなら、それはアヒル」——型ではなくメソッドが重要
- マジックメソッド:
__str__(出力)、__len__(長さ)、__getitem__(インデックス)、__eq__(等価性)、__add__(加算)など @dataclassは__init__、__repr__、__eq__を自動生成——純粋なデータクラスに最適
📝 練習問題
-
初級(難易度 ⭐):
Bookクラスを定義し、__str__と__len__(ページ数を返す)を実装。2 冊の本を作成し、print(book)とlen(book)を出力。 -
中級(難易度 ⭐⭐):
@dataclassを使ってProductクラス(name、price、quantity)を定義。__add__を実装——同じ名前の 2 つの製品をマージ(数量を合計、価格は平均)。ヒント: @dataclass クラス内で実装。 -
上級(難易度 ⭐⭐⭐):
Deckクラス(トランプのデッキ)を定義し、__len__、__getitem__(インデックスとスライスをサポート)、shuffle()(random.shuffleを使用)を実装。その後、デッキを作成し、最初の 5 枚のカードを手札として配るコードを書いてください。



