Python & AI Tutorials Logo
Python プログラミング

32. 継承とポリモーフィズムでクラスを拡張する

第30章では、現実世界の概念をモデル化するために自分でクラスを作る方法を学びました。BankAccountStudent のようなクラスを作り、データと振る舞いをひとまとめにしました。しかし、既存のクラスに似ているものの、いくつかの違いや追加がある新しいクラスを作る必要が出てきたらどうでしょうか?

継承(inheritance) は、既存のクラスを基に新しいクラスを作るための Python の仕組みです。コードをコピー&ペーストする代わりに、サブクラス(subclass) を作れば、親クラス(parent class)ベースクラス(base class)スーパークラス(superclass) とも呼ばれます)からすべての属性とメソッドを自動的に受け継ぎ、必要なものを追加したり修正したりできます。

この章では、継承によって関連するクラスの階層を構築できること、継承した振る舞いをどのようにカスタマイズするか、そして共通のインターフェースを共有しているときに ポリモーフィズム(polymorphism) によって異なるクラスを入れ替え可能に扱えることを見ていきます。

32.1) 既存クラスからサブクラスを作成する

32.1.1) 継承の基本構文

継承により、既存のクラス(親クラス(parent class) または ベースクラス(base class))を基に、新しいクラス(サブクラス(subclass) または 子クラス(child class))を作れます。サブクラスは親クラスからすべてのメソッドと属性を自動的に継承します。

サブクラスを作るときは、クラス名の後ろの丸括弧に親クラスを指定します。

python
# 親クラス
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} makes a sound"
 
# Animal を継承するサブクラス
class Dog(Animal):
    pass  # まだ追加コードは不要
 
# Dog のインスタンスを作成
buddy = Dog("Buddy")
print(buddy.speak())  # Output: Buddy makes a sound
print(buddy.name)     # Output: Buddy

Dog には独自のコードがなく(pass だけでも)、Animal からすべてを継承します。Dog クラスには、親から __init__ メソッドと speak メソッドが自動的に備わります。

Animal

+name

+init(name)

+speak()

Dog

32.1.2) なぜ継承が重要なのか

継承は、よくあるプログラミングの問題であるコードの重複を解決します。たとえば、さまざまな種類の従業員を管理するシステムを作っているとしましょう。

python
# 継承なし - 重複がたくさん
class FullTimeEmployee:
    def __init__(self, name, employee_id, salary):
        self.name = name
        self.employee_id = employee_id
        self.salary = salary
    
    def get_info(self):
        return f"{self.name} (ID: {self.employee_id})"
 
class PartTimeEmployee:
    def __init__(self, name, employee_id, hourly_rate):
        self.name = name
        self.employee_id = employee_id
        self.hourly_rate = hourly_rate
    
    def get_info(self):
        return f"{self.name} (ID: {self.employee_id})"

nameemployee_idget_info() が重複しているのが分かります。継承を使えば、この重複をなくせます。

python
# 継承あり - 共有コードを親クラスにまとめる
class Employee:
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id
    
    def get_info(self):
        return f"{self.name} (ID: {self.employee_id})"
 
class FullTimeEmployee(Employee):
    def __init__(self, name, employee_id, salary):
        Employee.__init__(self, name, employee_id)  # 親の __init__ を呼び出す
        # Note: We'll learn a better way to do this with super() in Section 32.3
        self.salary = salary
 
class PartTimeEmployee(Employee):
    def __init__(self, name, employee_id, hourly_rate):
        Employee.__init__(self, name, employee_id)  # 親の __init__ を呼び出す
        self.hourly_rate = hourly_rate
 
# 両方のサブクラスが get_info() を継承する
alice = FullTimeEmployee("Alice", "E001", 75000)
bob = PartTimeEmployee("Bob", "E002", 25)
 
print(alice.get_info())  # Output: Alice (ID: E001)
print(bob.get_info())    # Output: Bob (ID: E002)

これで、共通の属性とメソッドは Employee に置かれ、各サブクラスは固有の部分だけを定義すればよくなります。

32.1.3) サブクラスに新しいメソッドを追加する

サブクラスは、親にない独自のメソッドを追加できます。

python
class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
    
    def get_description(self):
        return f"{self.brand} {self.model}"
 
class Car(Vehicle):
    def __init__(self, brand, model, num_doors):
        Vehicle.__init__(self, brand, model)
        self.num_doors = num_doors
    
    def honk(self):  # Car 固有の新しいメソッド
        return "Beep beep!"
 
class Motorcycle(Vehicle):
    def __init__(self, brand, model, has_sidecar):
        Vehicle.__init__(self, brand, model)
        self.has_sidecar = has_sidecar
    
    def rev_engine(self):  # Motorcycle 固有の新しいメソッド
        return "Vroom vroom!"
 
# 各サブクラスは親のメソッドに加え、自分のメソッドも持つ
my_car = Car("Toyota", "Camry", 4)
print(my_car.get_description())  # Output: Toyota Camry
print(my_car.honk())             # Output: Beep beep!
 
my_bike = Motorcycle("Harley", "Sportster", False)
print(my_bike.get_description())  # Output: Harley Sportster
print(my_bike.rev_engine())       # Output: Vroom vroom!

Car クラスには get_description()(継承)と honk()(独自)が両方あります。Motorcycle クラスには get_description()(継承)と rev_engine()(独自)があります。

32.1.4) サブクラスに新しい属性を追加する

サブクラスは独自のインスタンス属性も追加できます。通常はサブクラスの __init__ メソッドで行います。

python
class BankAccount:
    def __init__(self, account_number, balance):
        self.account_number = account_number
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
        return self.balance
 
class SavingsAccount(BankAccount):
    def __init__(self, account_number, balance, interest_rate):
        BankAccount.__init__(self, account_number, balance)
        self.interest_rate = interest_rate  # 新しい属性
    
    def apply_interest(self):  # 新しい属性を使う新しいメソッド
        interest = self.balance * self.interest_rate
        self.balance += interest
        return interest
 
# SavingsAccount は BankAccount の全属性に加えて interest_rate も持つ
savings = SavingsAccount("SA001", 1000, 0.03)
savings.deposit(500)  # 継承したメソッド
print(f"Balance: ${savings.balance}")  # Output: Balance: $1500
 
interest_earned = savings.apply_interest()
print(f"Interest earned: ${interest_earned:.2f}")  # Output: Interest earned: $45.00
print(f"New balance: ${savings.balance:.2f}")      # Output: New balance: $1545.00

SavingsAccount には BankAccount からの account_numberbalance に加えて、独自の interest_rate 属性があります。

32.1.5) 複数レベルの継承

クラスは、他のクラスを継承しているクラスから継承することもでき、継承階層を作れます。

python
class LivingThing:
    def __init__(self, name):
        self.name = name
    
    def is_alive(self):
        return True
 
class Animal(LivingThing):
    def __init__(self, name, species):
        LivingThing.__init__(self, name)
        self.species = species
    
    def move(self):
        return f"{self.name} is moving"
 
class Dog(Animal):
    def __init__(self, name, breed):
        Animal.__init__(self, name, "Dog")
        self.breed = breed
    
    def bark(self):
        return f"{self.name} says: Woof!"
 
# Dog は Animal を継承し、Animal は LivingThing を継承する
max_dog = Dog("Max", "Golden Retriever")
 
# 3階層すべてのメソッドが動く
print(max_dog.is_alive())  # Output: True (from LivingThing)
print(max_dog.move())      # Output: Max is moving (from Animal)
print(max_dog.bark())      # Output: Max says: Woof! (from Dog)
 
# 3階層すべての属性が存在する
print(max_dog.name)     # Output: Max (from LivingThing)
print(max_dog.species)  # Output: Dog (from Animal)
print(max_dog.breed)    # Output: Golden Retriever (from Dog)

LivingThing

+name

+is_alive()

Animal

+species

+move()

Dog

+breed

+bark()

DogAnimal を継承し、AnimalLivingThing を継承しています。つまり Dog は、両方の親クラスからのメソッドと属性にアクセスできます。

32.2) サブクラスでメソッドをオーバーライドする

32.2.1) メソッドのオーバーライドとは

サブクラスが、継承したメソッドの動きを変更する必要がある場合があります。メソッドのオーバーライド(method overriding) とは、親クラスにあるメソッドと同じ名前のメソッドをサブクラスで定義することです。サブクラスのインスタンスでそのメソッドを呼ぶと、Python は親のものではなくサブクラス版を使います。

python
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} makes a sound"
 
class Dog(Animal):
    def speak(self):  # 親の speak メソッドをオーバーライドする
        return f"{self.name} says: Woof!"
 
class Cat(Animal):
    def speak(self):  # 別の振る舞いでオーバーライドする
        return f"{self.name} says: Meow!"
 
# 各クラスがそれぞれの speak() を持つ
generic_animal = Animal("Generic")
print(generic_animal.speak())  # Output: Generic makes a sound
 
buddy = Dog("Buddy")
print(buddy.speak())  # Output: Buddy says: Woof!
 
whiskers = Cat("Whiskers")
print(whiskers.speak())  # Output: Whiskers says: Meow!

buddy.speak() を呼ぶと、buddyDog のインスタンスなので、Python はまず Dog クラスの中に speak メソッドがあるか探します。Dog は独自の speak メソッドを定義しているため、Python はそのバージョンを使います。もし Dogspeak がなければ、その次に親クラス Animal を探し、代わりにそちらのバージョンを使います。

この探索順序(インスタンスのクラスから始めて、次に親クラスへ進む)が、メソッドのオーバーライドが働く仕組みであり、サブクラスが継承した振る舞いをカスタマイズする方法です。

32.2.2) なぜメソッドをオーバーライドするのか

メソッドのオーバーライドにより、一般的な振る舞いを特化させたバージョンを作れます。図形の階層を考えてみましょう。

python
class Shape:
    def __init__(self, name):
        self.name = name
    
    def area(self):
        return 0  # デフォルト実装
    
    def describe(self):
        return f"{self.name} with area {self.area()}"
 
class Rectangle(Shape):
    def __init__(self, width, height):
        Shape.__init__(self, "Rectangle")
        self.width = width
        self.height = height
    
    def area(self):  # 長方形向けの計算でオーバーライドする
        return self.width * self.height
 
class Circle(Shape):
    def __init__(self, radius):
        Shape.__init__(self, "Circle")
        self.radius = radius
    
    def area(self):  # 円向けの計算でオーバーライドする
        return 3.14159 * self.radius ** 2
 
# 図形ごとに面積の計算が異なる
rect = Rectangle(5, 3)
print(rect.describe())  # Output: Rectangle with area 15
 
circle = Circle(4)
print(circle.describe())  # Output: Circle with area 50.26544

describe() メソッドは両方のサブクラスに継承されますが、各サブクラスがそれぞれ独自の area() 実装を提供するため、正しく動作します。

rect.describe() を呼ぶと、継承された describe() メソッドが実行されますが、selfRectangle のインスタンスを参照します。そのため、describe()self.area() を呼ぶと、Python はまず Rectangle クラスの area() を探し、オーバーライドされたバージョンを見つけます。

32.2.3) __init__ のオーバーライドと親の初期化呼び出し

__init__ をオーバーライドする場合、通常は親の __init__ を呼んで、親側の初期化が確実に行われるようにする必要があります。

python
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def introduce(self):
        return f"I'm {self.name}, {self.age} years old"
 
class Student(Person):
    def __init__(self, name, age, student_id, major):
        # 親の __init__ を呼び出して name と age を設定する
        Person.__init__(self, name, age)
        # 次に student 固有の属性を設定する
        self.student_id = student_id
        self.major = major
    
alice = Student("Alice", 20, "S12345", "Computer Science")
print(alice.name)        # Output: Alice
print(alice.student_id)  # Output: S12345

Student.__init__ はまず Person.__init__(self, name, age) を呼んで親クラスの属性を初期化し、その後に独自の属性を追加している点に注目してください。

32.3) super() を使って親の振る舞いにアクセスする

32.3.1) super() の役割

前のセクションでは、親メソッドを ParentClass.method(self, ...) のように明示的に呼び出しました。Python には、よりきれいな方法として super() 関数があります。super() は、一時的なオブジェクトを返し、親クラス名を明示せずに親クラスのメソッドを呼べるようにします。

python
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} makes a sound"
 
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # Animal.__init__(self, name) よりも簡潔
        self.breed = breed
    
    def speak(self):
        parent_sound = super().speak()  # 親の speak() を呼ぶ
        return f"{parent_sound} - specifically, Woof!"
 
buddy = Dog("Buddy", "Labrador")
print(buddy.speak())
# Output: Buddy makes a sound - specifically, Woof!

super() を使う利点はいくつかあります。

  • 親クラス名を明示する必要がない
  • 多重継承でも正しく動作する(後で扱います)
  • 親クラスを変更しても保守しやすい

32.3.2) __init__ で super() を使う

super() の最も一般的な使い方は、親の __init__ を呼び出すことです。

python
class Employee:
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id
        self.is_active = True
    
    def deactivate(self):
        self.is_active = False
 
class Manager(Employee):
    def __init__(self, name, employee_id, department):
        super().__init__(name, employee_id)  # 親の属性を初期化する
        self.department = department
        self.team = []  # Manager 固有の属性
    
    def add_team_member(self, employee):
        self.team.append(employee)
 
# Manager は Employee の全属性に加えて独自の属性も持つ
sarah = Manager("Sarah", "M001", "Engineering")
print(sarah.name)        # Output: Sarah
print(sarah.is_active)   # Output: True
print(sarah.department)  # Output: Engineering

super().__init__(name, employee_id) を呼び出すことで、Manager クラスは Employee の初期化ロジック(is_activeTrue に設定することも含む)がすべて実行されることを保証します。

32.3.3) super() で親メソッドを拡張する

super() を使えば、親のメソッドを完全に置き換えるのではなく、拡張できます。

python
class BankAccount:
    def __init__(self, account_number, balance):
        self.account_number = account_number
        self.balance = balance
        self.transaction_count = 0
    
    def deposit(self, amount):
        self.balance += amount
        self.transaction_count += 1
        return self.balance
 
class CheckingAccount(BankAccount):
    def __init__(self, account_number, balance, overdraft_limit):
        super().__init__(account_number, balance)
        self.overdraft_limit = overdraft_limit
    
    def deposit(self, amount):
        was_overdrawn = self.balance < 0
        
        # 親の deposit を呼んで基本ロジックを処理する
        new_balance = super().deposit(amount)
        
        # 当座預金固有の振る舞いを追加する
        if was_overdrawn and new_balance >= 0:
            print("Account is no longer overdrawn")
        return new_balance
 
checking = CheckingAccount("C001", -50, 100)
checking.deposit(75)
# Output: Account is no longer overdrawn
print(f"Balance: ${checking.balance}")  # Output: Balance: $25
print(f"Transactions: {checking.transaction_count}")  # Output: Transactions: 1

CheckingAccount.deposit()super().deposit(amount) を呼んで基本的な入金ロジック(残高と取引回数の更新)を処理し、その後に独自の当座貸越状況のチェックを追加しています。

32.3.4) super() と親の直接呼び出しはいつ使い分けるか

ほとんどの場合は super() を使います。

python
class Vehicle:
    def __init__(self, brand):
        self.brand = brand
 
class Car(Vehicle):
    def __init__(self, brand, model):
        super().__init__(brand)  # 推奨
        self.model = model

多重継承の場面で特定の親を呼ぶ必要があるとき(後で扱います)、またはどの親を呼ぶかを明示したいときは、親を直接呼び出します。

python
class Car(Vehicle):
    def __init__(self, brand, model):
        Vehicle.__init__(self, brand)  # 明示的だが柔軟性は低い
        self.model = model

単一継承(親が1つ)の場合、super() がほぼ常により良い選択です。

32.3.5) 他のメソッドでの super()

super()__init__ だけでなく、どのメソッドにも使えます。

python
class TextProcessor:
    def process(self, text):
        # 基本処理: 前後の空白を取り除く
        return text.strip()
 
class UppercaseProcessor(TextProcessor):
    def process(self, text):
        # まず親の処理を行う
        processed = super().process(text)
        # 次に大文字変換を追加する
        return processed.upper()
 
class PrefixProcessor(UppercaseProcessor):
    def __init__(self, prefix):
        self.prefix = prefix
    
    def process(self, text):
        # まず親の処理を行う(親がさらに親も呼ぶ)
        processed = super().process(text)
        # 次にプレフィックスを追加する
        return f"{self.prefix}: {processed}"
 
processor = PrefixProcessor("ALERT")
result = processor.process("  system error  ")
print(result)  # Output: ALERT: SYSTEM ERROR

32.4) ポリモーフィズム: 互換性のあるクラスを扱う

32.4.1) ポリモーフィズムとは

ポリモーフィズム(ギリシャ語で「多くの形」)とは、同じメソッドを提供していれば、異なるクラスのオブジェクトを同じように扱える能力のことです。

Python では、複数のクラスが同じ名前のメソッドを持っていれば、オブジェクトの正確なクラスを知らなくてもそのメソッドを呼び出せます。

python
class Dog:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} says: Woof!"
 
class Cat:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} says: Meow!"
 
class Bird:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} says: Tweet!"
 
# speak() メソッドを持つ任意のオブジェクトで動作する関数
def make_animal_speak(animal):
    print(animal.speak())
 
# 異なるクラスでも動く
buddy = Dog("Buddy")
whiskers = Cat("Whiskers")
tweety = Bird("Tweety")
 
make_animal_speak(buddy)     # Output: Buddy says: Woof!
make_animal_speak(whiskers)  # Output: Whiskers says: Meow!
make_animal_speak(tweety)    # Output: Tweety says: Tweet!

make_animal_speak() 関数は、animal 引数が何のクラスかを気にしません。必要なのは、そのオブジェクトが speak() メソッドを持つことだけです。これがポリモーフィズムです。

32.4.2) 継承と組み合わせたポリモーフィズム

ポリモーフィズムは、サブクラスが親のメソッドをオーバーライドする継承と組み合わせると特に強力です。

python
class PaymentMethod:
    def process_payment(self, amount):
        return f"Processing ${amount:.2f}"
 
class CreditCard(PaymentMethod):
    def __init__(self, card_number):
        self.card_number = card_number
    
    def process_payment(self, amount):
        return f"Charging ${amount:.2f} to credit card ending in {self.card_number[-4:]}"
 
class PayPal(PaymentMethod):
    def __init__(self, email):
        self.email = email
    
    def process_payment(self, amount):
        return f"Sending ${amount:.2f} via PayPal to {self.email}"
 
class BankTransfer(PaymentMethod):
    def __init__(self, account_number):
        self.account_number = account_number
    
    def process_payment(self, amount):
        return f"Transferring ${amount:.2f} to account {self.account_number}"
 
# 任意の PaymentMethod で動作する関数
def complete_purchase(payment_method, amount):
    print(payment_method.process_payment(amount))
    print("Purchase complete!")
 
# どの支払い方法でも同じ関数で動く
credit = CreditCard("1234567890123456")
paypal = PayPal("user@example.com")
bank = BankTransfer("9876543210")
 
complete_purchase(credit, 99.99)
# Output: Charging $99.99 to credit card ending in 3456
# Output: Purchase complete!
 
complete_purchase(paypal, 49.50)
# Output: Sending $49.50 via PayPal to user@example.com
# Output: Purchase complete!
 
complete_purchase(bank, 199.00)
# Output: Transferring $199.00 to account 9876543210
# Output: Purchase complete!

complete_purchase() 関数は、任意の PaymentMethod サブクラスで動作します。各サブクラスは process_payment() を独自に実装しますが、この関数はどの具体的なクラスを扱っているかを知る必要がありません。

32.4.3) ダックタイピング(duck typing):「アヒルのように歩けば...」

Python のポリモーフィズムは、クラス同士が継承で関係していることを必須としません。これを ダックタイピング(duck typing) と呼びます。「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」という考え方です。つまり Python が重視するのは、オブジェクトが「何であるか」(クラス)ではなく「何ができるか」(メソッド)です。必要なメソッドを持っていれば、そのクラス階層に関係なく利用できます。

python
class FileWriter:
    def __init__(self, filename):
        self.filename = filename
    
    def write(self, data):
        print(f"Writing to {self.filename}: {data}")
 
class DatabaseWriter:
    def __init__(self, table_name):
        self.table_name = table_name
    
    def write(self, data):
        print(f"Inserting into {self.table_name}: {data}")
 
class ConsoleWriter:
    def write(self, data):
        print(f"Console output: {data}")
 
# write() メソッドを持つ任意のオブジェクトで動作する関数
def save_data(writer, data):
    writer.write(data)
 
# 3つのクラスは無関係でも、どれも動く
file_writer = FileWriter("data.txt")
db_writer = DatabaseWriter("users")
console_writer = ConsoleWriter()
 
save_data(file_writer, "User data")
# Output: Writing to data.txt: User data
 
save_data(db_writer, "User data")
# Output: Inserting into users: User data
 
save_data(console_writer, "User data")
# Output: Console output: User data

これらのクラスはいずれも共通の親クラスを継承していませんが、すべて write() メソッドを持つため save_data() で扱えます。これがダックタイピングであり、関数が気にするのはクラスではなくインターフェース(利用できるメソッド)だけです。

32.4.4) 実用例: プラグインシステム

ポリモーフィズムにより、柔軟で拡張しやすいシステムを作れます。以下は、データ処理のためのシンプルなプラグインシステムです。

python
class DataProcessor:
    def process(self, data):
        return data  # ベース実装は何もしない
 
class UppercaseProcessor(DataProcessor):
    def process(self, data):
        return data.upper()
 
class ReverseProcessor(DataProcessor):
    def process(self, data):
        return data[::-1]
 
class RemoveSpacesProcessor(DataProcessor):
    def process(self, data):
        return data.replace(" ", "")
 
class DataPipeline:
    def __init__(self):
        self.processors = []
    
    def add_processor(self, processor):
        self.processors.append(processor)
    
    def run(self, data):
        result = data
        for processor in self.processors:
            result = processor.process(result)  # ポリモーフィックな呼び出し
        return result
 
# 処理パイプラインを構築する
pipeline = DataPipeline()
pipeline.add_processor(UppercaseProcessor())
pipeline.add_processor(RemoveSpacesProcessor())
pipeline.add_processor(ReverseProcessor())
 
# パイプラインを通してデータを処理する
input_data = "Hello World"
output = pipeline.run(input_data)
print(f"Input:  {input_data}")   # Output: Input:  Hello World
print(f"Output: {output}")        # Output: Output: DLROWOLLEH

DataPipeline は、どの具体的なプロセッサを含んでいるかを知る必要がなく、各要素の process() を呼ぶだけです。パイプラインのコードを変えずに、新しい種類のプロセッサを簡単に追加できます。

32.5) 型とクラス関係をチェックする (isinstance, issubclass)

32.5.1) isinstance() でインスタンスの型をチェックする

オブジェクトが特定のクラスのインスタンスかどうかを確認したい場合があります。isinstance() 関数はこれを行います。

python
class Animal:
    pass
 
class Dog(Animal):
    pass
 
class Cat(Animal):
    pass
 
buddy = Dog()
whiskers = Cat()
 
# オブジェクトがクラスのインスタンスかどうかをチェックする
print(isinstance(buddy, Dog))     # Output: True
print(isinstance(buddy, Animal))  # Output: True (Dog inherits from Animal)
print(isinstance(buddy, Cat))     # Output: False
 
print(isinstance(whiskers, Cat))    # Output: True
print(isinstance(whiskers, Animal)) # Output: True
print(isinstance(whiskers, Dog))    # Output: False

buddyDog のインスタンスなのに、isinstance(buddy, Animal)True を返す点に注目してください。これは DogAnimal を継承しているため、Dog のインスタンスは Animal のインスタンスとも見なされるからです。

32.5.2) なぜ isinstance() は継承を考慮するのか

isinstance() 関数は、継承チェーン全体をチェックします。

python
class Vehicle:
    pass
 
class Car(Vehicle):
    pass
 
class ElectricCar(Car):
    pass
 
tesla = ElectricCar()
 
# 継承のすべての階層をチェックする
print(isinstance(tesla, ElectricCar))  # Output: True
print(isinstance(tesla, Car))          # Output: True
print(isinstance(tesla, Vehicle))      # Output: True
print(isinstance(tesla, str))          # Output: False

is a

is a

is a

tesla インスタンス

ElectricCar

Car

Vehicle

tesla オブジェクトは ElectricCar のインスタンスですが、継承のため CarVehicle のインスタンスでもあります。

32.5.3) 複数の型をまとめてチェックする

タプルを渡すことで、オブジェクトが複数クラスのいずれかのインスタンスかどうかをチェックできます。

python
class Dog:
    pass
 
class Cat:
    pass
 
class Bird:
    pass
 
def is_pet(animal):
    return isinstance(animal, (Dog, Cat, Bird))
 
buddy = Dog()
whiskers = Cat()
tweety = Bird()
rock = "just a rock"
 
print(is_pet(buddy))     # Output: True
print(is_pet(whiskers))  # Output: True
print(is_pet(tweety))    # Output: True
print(is_pet(rock))      # Output: False

これは isinstance(animal, Dog) or isinstance(animal, Cat) or isinstance(animal, Bird) と書くよりも簡潔です。

32.5.4) issubclass() でクラス関係をチェックする

issubclass() 関数は、あるクラスが別のクラスのサブクラスかどうかをチェックします。

python
class Animal:
    pass
 
class Dog(Animal):
    pass
 
class Cat(Animal):
    pass
 
class Poodle(Dog):
    pass
 
# クラス関係をチェックする
print(issubclass(Dog, Animal))    # Output: True
print(issubclass(Cat, Animal))    # Output: True
print(issubclass(Poodle, Dog))    # Output: True
print(issubclass(Poodle, Animal)) # Output: True (indirect inheritance)
print(issubclass(Dog, Cat))       # Output: False
 
# クラスは自分自身のサブクラスと見なされる
print(issubclass(Dog, Dog))       # Output: True

issubclass() はインスタンスではなくクラスに対して動作する点に注意してください。インスタンスには isinstance()、クラスには issubclass() を使います。

32.5.5) 型チェックの実用的なユースケース

型チェックは、型によって異なる振る舞いが必要な場合に役立ちます。

python
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
 
class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus
 
class Contractor:
    def __init__(self, name, hourly_rate, hours):
        self.name = name
        self.hourly_rate = hourly_rate
        self.hours = hours
 
def calculate_payment(worker):
    # 継承がある場合に isinstance() を使うときは、親クラスより先にサブクラスをチェックする
    if isinstance(worker, Manager):  # まず Manager をチェック
        return worker.salary + worker.bonus
    elif isinstance(worker, Employee):  # 次に Employee(親クラス)をチェック
        return worker.salary
    elif isinstance(worker, Contractor):
        return worker.hourly_rate * worker.hours
    else:
        return 0
 
alice = Employee("Alice", 50000)
bob = Manager("Bob", 70000, 10000)
charlie = Contractor("Charlie", 50, 160)
 
print(f"Alice's payment: ${calculate_payment(alice)}")    # Output: Alice's payment: $50000
print(f"Bob's payment: ${calculate_payment(bob)}")        # Output: Bob's payment: $80000
print(f"Charlie's payment: ${calculate_payment(charlie)}")# Output: Charlie's payment: $8000

しかし、多くの場合、ポリモーフィズム(各クラスが共通のメソッドを実装すること)のほうが型チェックより良い選択です。

python
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    def calculate_payment(self):
        return self.salary
 
class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus
    
    def calculate_payment(self):
        return self.salary + self.bonus
 
class Contractor:
    def __init__(self, name, hourly_rate, hours):
        self.name = name
        self.hourly_rate = hourly_rate
        self.hours = hours
    
    def calculate_payment(self):
        return self.hourly_rate * self.hours
 
# 型チェックは不要 - ポリモーフィズムが処理する
workers = [
    Employee("Alice", 50000),
    Manager("Bob", 70000, 10000),
    Contractor("Charlie", 50, 160)
]
 
for worker in workers:
    payment = worker.calculate_payment()  # ポリモーフィックな呼び出し
    print(f"{worker.name}'s payment: ${payment}")

Output:

Alice's payment: $50000
Bob's payment: $80000
Charlie's payment: $8000

このポリモーフィックなアプローチは、より柔軟で、新しい worker 型を拡張しやすいです。新しい worker クラスを追加しても呼び出し側のコードを修正する必要はなく、calculate_payment() メソッドを持つようにするだけで済みます。


継承とポリモーフィズムは、コードを整理し、柔軟で拡張可能なシステムを作るための強力な道具です。サブクラスを作れば、既存コードを再利用しつつ、振る舞いを追加・変更できます。メソッドをオーバーライドすれば、サブクラスの動きをカスタマイズできます。そしてポリモーフィズムを使えば、共通インターフェースを通じて多くの異なるクラスで動作するコードを書けます。

重要なのは、これらの機能をよく考えて使うことです。

  • 「is-a」関係が本当にあるときに継承を使う(DogAnimal である)
  • クラスが行うことを完全に変えるのではなく、振る舞いを特化させるためにメソッドをオーバーライドする
  • 親の振る舞いを完全に置き換えるのではなく、super() で拡張する
  • 可能なら型チェックよりもポリモーフィズム(共通メソッド)を優先する

より大きなプログラムを作っていく中で、これらのオブジェクト指向の技法は、理解しやすく、保守しやすく、拡張しやすいコードを作る助けになります。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai