Python:継承と特殊メソッド
1学習の目的
- 継承で共通部分を親クラスにまとめ、違う部分だけ子クラスに書けるようになる。似たクラスを何個も書く重複がなくなる。
- __len__ や __eq__ などの特殊メソッドを定義し、自作クラスを len() や == といったPythonの標準機能に対応させられるようになる。
2基礎解説
| やりたいこと | 書き方 | 意味 |
|---|---|---|
| 継承する | class Dog(Animal): | 親の属性・メソッドを引き継ぐ |
| 親の初期化を呼ぶ | super().__init__(name) | 親の __init__ を実行する |
| 上書きする | 親と同名のメソッドを定義 | オーバーライド |
| 組み込み機能に対応 | __len__ / __eq__ / __add__ | len(x) / x == y / x + y |
- class 子(親): と書くだけで親の中身を全部引き継ぐ。共通部分は親、違う部分だけ子に書くのが設計の基本。
- 子で __init__ を定義したら必ず super().__init__(...) を呼ぶ。忘れると親が作るはずの属性が存在せず AttributeError になる。
- 親と同名のメソッドを子に書くと上書きされる。親の処理も使いたければ super().メソッド名() で呼び出せる。
- isinstance(obj, Animal) は「そのクラスまたはその子クラスのインスタンスか」を判定する。
- 特殊メソッドはPythonの標準構文と自作クラスをつなぐ橋。__len__ を書けば len() が、__add__ を書けば + が使えるようになる。
現場使用例:一般会員クラスからプレミアム会員を派生、共通のAPIクライアントから各サービス用を派生、Exception を継承した自作エラークラス、図形や従業員など「同じ仲間だが計算方法が違う」もの。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
def __str__(self):
return f"{self.name}({self.speak()})"
class Dog(Animal):
def speak(self): # 上書き
return "ワン"
class Cat(Animal):
def __init__(self, name, indoor):
super().__init__(name) # 親の初期化を呼ぶ
self.indoor = indoor
def speak(self):
return "ニャー"
print(Dog("ポチ")) # ポチ(ワン)
print(Cat("タマ", True)) # タマ(ニャー)
print(isinstance(Dog("ポチ"), Animal)) # True
3基本ドリル(10問)
出力 == ワン
class Dog(Animal): と書き、中で同名の speak を定義すれば上書きされる。
class Animal:
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return "ワン"
print(Dog().speak())
オーバーライドの最小形。親と同じ名前で定義するだけで、子のほうが優先される。
A. class Dog: Animal B. class Dog(Animal): C. class Dog extends Animal: D. class Dog -> Animal: 選択★☆☆無料
解答 == B
Pythonはカッコで親を指定する。
B
extends は Java や JavaScript の書き方。Pythonはクラス名の後ろに丸カッコで親を書く。
出力 == 私はポチです
中身を書かないクラスは pass と1行書けばよい。__init__ も introduce も親から引き継がれる。
class Animal:
def __init__(self, name):
self.name = name
def introduce(self):
return f"私は{self.name}です"
class Dog(Animal):
pass
print(Dog("ポチ").introduce())
何も書かなくても親の機能は全部使えるのが継承。まず親を継承だけしておき、必要になった部分から上書きしていく進め方ができる。
class Animal:
def __init__(self, name):
self.name = name
class Cat(Animal):
def __init__(self, name, indoor):
____.__init__(name)
self.indoor = indoor
c = Cat("タマ", True)
print(c.name)
出力 == タマ
「親(スーパークラス)」を表す組み込み関数。カッコを付けて呼ぶ。
super()
super() は親クラスを指す。ここで name を渡しているので、親の __init__ が self.name を作ってくれる。
A. 必ずエラーで止まる B. 親の __init__ が実行されず、親で作られるはずの属性が存在しない C. 自動的に呼ばれるので問題ない D. 子の __init__ が無視される 選択★☆☆無料
解答 == B
定義した時点ではエラーにならない。問題が出るのはいつか。
B
定義時ではなく、その属性を使った瞬間に AttributeError が出る。原因が離れた場所で発覚するので厄介。子で __init__ を書いたら super() を呼ぶ、と機械的に覚えるとよい。
出力 == 3
__len__ の中で return len(self.items) と書けばよい。
class Cart:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
cart = Cart(["ノート", "ペン", "消しゴム"])
print(len(cart))
cart.count() のような独自メソッドを覚えてもらう必要がなくなるのが特殊メソッドの価値。使う人は普通に len() と書けばよい。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def ____(self, other):
return self.x == other.x and self.y == other.y
print(Point(1, 2) == Point(1, 2))
出力 == True
equal(等しい)の略。前後にアンダースコア2つ。
__eq__
定義しないと、中身が同じでも別のオブジェクトなので False になる。データの一致を判定したいクラスには必要。
A. True B. False C. エラーになる D. None 選択★★☆無料
解答 == A
子クラスは親クラスの一種とみなされるか。
A
子は親の一種と判定される。だから「Animalのリスト」に Dog も Cat も混ぜて入れられる。
出力 == 10
子の discount の中で super().discount() を呼び、その結果に 10 を足す。
class Member:
def discount(self):
return 0
class Premium(Member):
def discount(self):
return super().discount() + 10
print(Premium().discount())
上書きしつつ親の処理も使う形。親のロジックが変わっても、子は自動的にその変更を引き継げる。
出力 == 1500
__add__ は新しい Money を返すようにする。return Money(self.amount + other.amount)。
class Money:
def __init__(self, amount):
self.amount = amount
def __add__(self, other):
return Money(self.amount + other.amount)
total = Money(1000) + Money(500)
print(total.amount)
元のオブジェクトを書き換えず新しいものを返すのが + の作法。1000 + 500 が元の数値を壊さないのと同じ考え方。
4実践シナリオ(5問)
田中(一般)・鈴木(プレミアム)・佐藤(VIP)の3人について、10,000円の商品の支払額を「田中(一般):10,000円」の形式で出力せよ。 コーディング★★☆無料
出力3行が完全一致
子の __init__ で super().__init__(name, ...) を呼び、種別と割引率だけを変える。計算と表示は親に1回書けばよい。
class Member:
def __init__(self, name, kind="一般", rate=0):
self.name = name
self.kind = kind
self.rate = rate
def final_price(self, price):
return int(price * (1 - self.rate))
def __str__(self):
return f"{self.name}({self.kind})"
class Premium(Member):
def __init__(self, name):
super().__init__(name, "プレミアム", 0.1)
class VIP(Member):
def __init__(self, name):
super().__init__(name, "VIP", 0.2)
members = [Member("田中"), Premium("鈴木"), VIP("佐藤")]
for m in members:
print(f"{m}:{m.final_price(10000):,}円")
計算式は親に1回だけ書けばよい。会員種別が10種類に増えても、子クラスは __init__ の1行ずつで済む。PY04で if/elif を並べた割引処理と見比べてほしい。
出力3行が完全一致
子の __init__ で super().__init__("長方形") のように名前を渡す。合計は sum(s.area() for s in shapes)。
class Shape:
def __init__(self, name):
self.name = name
def area(self):
return 0
def __str__(self):
return f"{self.name}:{self.area():.2f}"
class Rectangle(Shape):
def __init__(self, w, h):
super().__init__("長方形")
self.w = w
self.h = h
def area(self):
return self.w * self.h
class Circle(Shape):
def __init__(self, r):
super().__init__("円")
self.r = r
def area(self):
return self.r ** 2 * 3.14
shapes = [Rectangle(4, 6), Circle(5)]
for s in shapes:
print(s)
print(f"合計面積:{sum(s.area() for s in shapes):.2f}")
種類の違う図形を同じリストに入れて同じように扱えるのが継承の威力。print(s) と書くだけで、それぞれが自分の計算方法で面積を出す。これをポリモーフィズム(多態性)と呼ぶ。
在庫5の「ノート」に対して3個・5個の順に出荷を試み、「出荷しました:3個」「エラー:在庫が足りません(残り2個)」の2行を出力せよ。 コーディング★★☆無料
出力2行が完全一致
class StockError(Exception): pass の2行で自作エラーが作れる。呼び出し側は except StockError as e: で受け止める。
class StockError(Exception):
pass
class Product:
def __init__(self, name, stock):
self.name = name
self.stock = stock
def ship(self, amount):
if amount > self.stock:
raise StockError(f"在庫が足りません(残り{self.stock}個)")
self.stock -= amount
return amount
p = Product("ノート", 5)
for amount in [3, 5]:
try:
print(f"出荷しました:{p.ship(amount)}個")
except StockError as e:
print(f"エラー:{e}")
自作エラーは2行で作れる。ValueError で済ませず専用のエラーを作ると、呼び出し側が「在庫の問題だけ」を選んで捕まえられる。PY10の例外処理と継承がつながる場面。
カートA[ノート1500, マウス3200] とカートB[キーボード3200] を作り、A・B・A+B の3行を出力すること。 コーディング★★★無料
出力3行が完全一致
total は sum(price for _, price in self.items)。__add__ では self.items + other.items でリストを連結した新しい Cart を返す。__str__ の中では len(self) が使える。
class Cart:
def __init__(self, name, items):
self.name = name
self.items = items
def total(self):
return sum(price for _, price in self.items)
def __len__(self):
return len(self.items)
def __add__(self, other):
return Cart("合計", self.items + other.items)
def __str__(self):
return f"{self.name}:{len(self)}点 / {self.total():,}円"
a = Cart("カートA", [("ノート", 1500), ("マウス", 3200)])
b = Cart("カートB", [("キーボード", 3200)])
print(a)
print(b)
print(a + b)
a + b と書けるようになったのが成果。使う人はカートの内部構造を知らなくても直感的に扱える。タプルの分解で名前が不要なときは _ という変数名を使う慣習も覚えておきたい。
田中(月給300,000)・鈴木(時給1,600円×100時間)・佐藤(月給350,000)の3人を出力し、最後に「合計人件費:810,000円」と出力すること。 コーディング★★★無料
出力4行が完全一致
子の __init__ で super().__init__(name, "正社員") のように区分を渡す。salary() を子ごとに上書きし、合計は sum(s.salary() for s in staff)。
class Employee:
def __init__(self, name, kind):
self.name = name
self.kind = kind
def salary(self):
return 0
def __str__(self):
return f"{self.name}({self.kind}):{self.salary():,}円"
class SalariedEmployee(Employee):
def __init__(self, name, monthly):
super().__init__(name, "正社員")
self.monthly = monthly
def salary(self):
return self.monthly
class HourlyEmployee(Employee):
def __init__(self, name, wage, hours):
super().__init__(name, "時給")
self.wage = wage
self.hours = hours
def salary(self):
return self.wage * self.hours
staff = [
SalariedEmployee("田中", 300000),
HourlyEmployee("鈴木", 1600, 100),
SalariedEmployee("佐藤", 350000),
]
for s in staff:
print(s)
print(f"合計人件費:{sum(s.salary() for s in staff):,}円")
給与の計算方法が違っても、集計側は salary() を呼ぶだけ。歩合制や業務委託が増えても、集計コードは1文字も変えずに済む。これが「拡張に強い設計」の具体例。
5仕上げ課題
OutOfStockError:Exception を継承した自作エラー
Product クラス(親):属性 name / price / stock
・label() … "商品" を返す ・needs_shipping() … False を返す
・take(qty) … 在庫不足なら raise OutOfStockError(f"「{name}」の在庫が足りません(残り{stock}個)")、足りていれば在庫を減らす
DigitalProduct:label は "電子"(送料は不要のまま)
PhysicalProduct:label は "物理"、needs_shipping は True
Order クラス:属性 store / lines(空リスト)
・add(product, qty) … take してから lines に (product, qty) を追加
・__len__ … 合計点数 ・subtotal() … 商品小計
・shipping() … 送料が必要な商品が1つでもあれば800、なければ0
・total() … 小計+送料
処理:ノートPC(128000, 在庫5)×1、マウス(3200, 在庫2)×2、Python入門eBook(2400, 在庫999)×1 を注文して明細と集計を表示 → 最後にマウスをもう1個追加しようとして失敗する。
出力仕様(11行):
=== ゼロカラストア 注文 ===
[物理] ノートPC:128,000円 × 1
[物理] マウス:3,200円 × 2
[電子] Python入門eBook:2,400円 × 1
---
商品点数:4点
商品小計:136,800円
送料:800円
合計:137,600円
---
エラー:「マウス」の在庫が足りません(残り0個)
出力11行が完全一致
送料判定は for で lines を回し、needs_shipping() が True の商品が見つかった時点で 800 を return する。明細は for p, q in order.lines: でタプルを分解して取り出す。最後の追加は try / except OutOfStockError as e: で受け止める。
class OutOfStockError(Exception):
pass
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def label(self):
return "商品"
def needs_shipping(self):
return False
def take(self, qty):
if qty > self.stock:
raise OutOfStockError(f"「{self.name}」の在庫が足りません(残り{self.stock}個)")
self.stock -= qty
class DigitalProduct(Product):
def label(self):
return "電子"
class PhysicalProduct(Product):
def label(self):
return "物理"
def needs_shipping(self):
return True
class Order:
def __init__(self, store):
self.store = store
self.lines = []
def add(self, product, qty):
product.take(qty)
self.lines.append((product, qty))
def __len__(self):
return sum(qty for _, qty in self.lines)
def subtotal(self):
return sum(p.price * q for p, q in self.lines)
def shipping(self):
for p, _ in self.lines:
if p.needs_shipping():
return 800
return 0
def total(self):
return self.subtotal() + self.shipping()
notepc = PhysicalProduct("ノートPC", 128000, 5)
mouse = PhysicalProduct("マウス", 3200, 2)
ebook = DigitalProduct("Python入門eBook", 2400, 999)
order = Order("ゼロカラストア")
order.add(notepc, 1)
order.add(mouse, 2)
order.add(ebook, 1)
print(f"=== {order.store} 注文 ===")
for p, q in order.lines:
print(f"[{p.label()}] {p.name}:{p.price:,}円 × {q}")
print("---")
print(f"商品点数:{len(order)}点")
print(f"商品小計:{order.subtotal():,}円")
print(f"送料:{order.shipping():,}円")
print(f"合計:{order.total():,}円")
print("---")
try:
order.add(mouse, 1)
except OutOfStockError as e:
print(f"エラー:{e}")
電子書籍と物理商品という性質の違うものを、Order は区別せず同じように扱えているのが最大の到達点。Order のコードには「電子なら」「物理なら」という分岐が一切ない。送料の要否は商品自身が答えるので、将来「定期購読商品」が増えても Order は無修正で動く。継承(親子)・ポリモーフィズム(同じ呼び方で違う動作)・特殊メソッド(len)・自作例外——オブジェクト指向の主要な道具がすべて噛み合った形で、ここまで書ければ実務のシステム設計の入口に立っている。