Python 継承とポリモーフィズム

2025-10-27

はじめに

今回はオブジェクト指向プログラミングの「継承」と「ポリモーフィズム」について詳しく探っていきます。これらの概念は、コードの再利用性を高め、柔軟で拡張性の高いプログラムを構築するための強力なツールです。

現実世界で例えるなら、継承は「親から子へ特徴が受け継がれる」関係に似ています。例えば、「自動車」という基本クラスがあり、そこから「スポーツカー」「SUV」「トラック」などの特殊なクラスが派生するようなイメージです。

継承の基本

継承とは、既存のクラスを基にして新しいクラスを作成する仕組みです。基になるクラスを「親クラス」「スーパークラス」「基底クラス」と呼び、新しく作成するクラスを「子クラス」「サブクラス」「派生クラス」と呼びます。

次のコードは 継承とメソッドのオーバーライド の基本例です。

Animal クラスを親クラスとして定義し、DogCat クラスがそれを継承しています。
共通の属性(name, age)やメソッド(sleep())は親クラスから引き継がれ、それぞれのクラスで speak() メソッドをオーバーライド(上書き) して、犬と猫に特有の鳴き声を出すように動作を変更しています。

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        print("動物の鳴き声")

    def sleep(self):
        print(f"{self.name}は眠っています")

class Dog(Animal):  # Animalクラスを継承
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # 親クラスのコンストラクタを呼び出し
        self.breed = breed

    def speak(self):  # メソッドのオーバーライド
        print(f"{self.name}はワンワン吠えます")

class Cat(Animal):  # Animalクラスを継承
    def __init__(self, name, age, color):
        super().__init__(name, age)
        self.color = color

    def speak(self):  # メソッドのオーバーライド
        print(f"{self.name}はニャーと鳴きます")

継承の利点

  1. コードの再利用: 親クラスの機能をそのまま利用できる
  2. 保守性の向上: 共通機能を一箇所で管理できる
  3. 拡張性: 新しい機能を簡単に追加できる
  4. 階層的な構造: 現実世界の関係を自然に表現できる

super()関数の役割

super()関数は、親クラスのメソッドを呼び出すための重要な関数です。特にコンストラクタで親クラスの初期化を行う際に頻繁に使用されます。

class Vehicle:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year
        self.speed = 0

    def start_engine(self):
        print("エンジンを始動しました")

    def stop_engine(self):
        print("エンジンを停止しました")

class Car(Vehicle):
    def __init__(self, brand, model, year, doors):
        super().__init__(brand, model, year)  # 親クラスのコンストラクタを呼び出し
        self.doors = doors  # 子クラス独自の属性

    def open_trunk(self):  # 子クラス独自のメソッド
        print("トランクを開けました")

# 使用例
my_car = Car("Toyota", "Camry", 2023, 4)
print(f"{my_car.brand} {my_car.model}")  # 親クラスの属性にアクセス
my_car.start_engine()  # 親クラスのメソッドを呼び出し

Vehicleクラスが共通の属性(ブランド、モデル、年式)や動作(エンジンの始動・停止)を定義し、Carクラスがそれを継承して自動車特有の属性doorsやメソッドopen_trunk()を追加しています。これにより、共通の機能を親クラスにまとめつつ、子クラスで独自の機能を拡張できる仕組みが理解できます

メソッドのオーバーライド

オーバーライドとは、親クラスで定義されたメソッドを子クラスで再定義することです。これにより、同じメソッド名でも異なる振る舞いを実現できます。オーバーライドにより、同じメソッド名でもクラスごとに異なる動作を実現でき、ポリモーフィズム(多態性)が成立します。以下のコードの例を参照します。

class Shape:
    def __init__(self, name):
        self.name = name

    def area(self):
        raise NotImplementedError("サブクラスで実装してください")

    def perimeter(self):
        raise NotImplementedError("サブクラスで実装してください")

    def display_info(self):
        print(f"図形: {self.name}")

class Rectangle(Shape):
    def __init__(self, width, height):
        super().__init__("長方形")
        self.width = width
        self.height = height

    def area(self):  # オーバーライド
        return self.width * self.height

    def perimeter(self):  # オーバーライド
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        super().__init__("円")
        self.radius = radius

    def area(self):  # オーバーライド
        return 3.14159 * self.radius ** 2

    def perimeter(self):  # オーバーライド
        return 2 * 3.14159 * self.radius

親クラスShapeで定義されたarea()perimeter()を、子クラスRectangleCircleがそれぞれの図形に合わせて再定義することで、同じメソッド名でも異なる処理を実現しています。これにより、ポリモーフィズムが成立し、例えばshapes = [Rectangle(3, 4), Circle(5)]のように異なる図形をまとめて扱っても、shape.area()の呼び出しだけで各図形に適した面積が計算されます。結果として、柔軟で拡張性の高いコード設計が可能になります。

ポリモーフィズムの概念

ポリモーフィズム(多態性)とは、同じインターフェースで異なる振る舞いを実現する仕組みです。異なるクラスのオブジェクトが同じメソッド名で呼び出されても、各クラスに適した動作を行うことができます。

ポリモーフィズムの実例

Birdクラスを基底に、SparrowEaglePenguinがそれぞれfly()メソッドを独自に定義しています。make_bird_fly()関数は同じfly()呼び出しを行いますが、実際の動作はオブジェクトの種類に応じて異なる結果を出力します。

class Bird:
    def fly(self):
        pass

class Sparrow(Bird):
    def fly(self):
        print("スズメはバサバサ飛びます")

class Eagle(Bird):
    def fly(self):
        print("ワシは悠然と飛びます")

class Penguin(Bird):
    def fly(self):
        print("ペンギンは飛べません")

def make_bird_fly(bird):
    bird.fly()  # 同じメソッド呼び出しでも、オブジェクトによって異なる動作

# 使用例
birds = [Sparrow(), Eagle(), Penguin()]

for bird in birds:
    make_bird_fly(bird)

出力結果:

スズメはバサバサ飛びます
ワシは悠然と飛びます
ペンギンは飛べません

多重継承

Pythonでは、複数の親クラスから継承する「多重継承」が可能です。これは強力な機能ですが、注意して使用する必要があります。

class Flyable:
    def fly(self):
        print("飛んでいます")

class Swimmable:
    def swim(self):
        print("泳いでいます")

class Duck(Flyable, Swimmable):
    def __init__(self, name):
        self.name = name

    def quack(self):
        print(f"{self.name}がガーガー鳴きます")

# 使用例
duck = Duck("ドナルド")
duck.fly()   # Flyableクラスのメソッド
duck.swim()  # Swimmableクラスのメソッド
duck.quack() # Duckクラス独自のメソッド

多重継承は、複数の親クラスから同時に機能を継承できる便利な仕組みですが、注意が必要です。
理由は次の通りです。

  1. メソッドの衝突:複数の親クラスで同名のメソッドがあると、どのクラスのメソッドが呼ばれるか分かりづらくなります。
  2. 呼び出し順序の複雑さ:Pythonは「MRO(Method Resolution Order)」というルールでメソッド探索順を決めますが、構造が複雑になると予期しない動作を引き起こすことがあります。
  3. 可読性・保守性の低下:継承関係が増えるほど、コードの理解や変更が難しくなります。

そのため、多重継承は慎重に設計し、必要最小限に使うのが望ましいです。

抽象基底クラス

抽象基底クラスは、インスタンス化できないクラスで、他のクラスのテンプレートとして機能します。Pythonではabcモジュールを使用して実現します。

abcモジュール」というのは正式名称ではなく、Python標準ライブラリに含まれるモジュール名 abc(=Abstract Base Classes の略)をそのまま通称として呼んでいるものです。

abcモジュールは、Pythonで**抽象基底クラス(Abstract Base Class)**を定義するためのモジュールです。共通のメソッドやインターフェースを強制することで、継承先のクラスが特定のメソッドを必ず実装するようにできます。@abstractmethodデコレータを使って抽象メソッドを定義します。

from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id

    @abstractmethod
    def calculate_salary(self):
        pass

    @abstractmethod
    def display_role(self):
        pass

    def display_info(self):
        print(f"名前: {self.name}")
        print(f"社員ID: {self.employee_id}")

class FullTimeEmployee(Employee):
    def __init__(self, name, employee_id, monthly_salary):
        super().__init__(name, employee_id)
        self.monthly_salary = monthly_salary

    def calculate_salary(self):
        return self.monthly_salary

    def display_role(self):
        print("正社員")

class PartTimeEmployee(Employee):
    def __init__(self, name, employee_id, hourly_wage, hours_worked):
        super().__init__(name, employee_id)
        self.hourly_wage = hourly_wage
        self.hours_worked = hours_worked

    def calculate_salary(self):
        return self.hourly_wage * self.hours_worked

    def display_role(self):
        print("パートタイム社員")

isinstance()とissubclass()

isinstance()とissubclass()関数は、継承関係を確認するために使用されます。isinstance() はオブジェクトが特定のクラス(またはそのサブクラス)のインスタンスかを判定し、issubclass() はクラス同士の継承関係を確認します。

ここでは my_carCarVehicle のインスタンスであり、CarVehicle のサブクラスであることがわかります。

# 継承関係の確認
print(isinstance(my_car, Car))      # True
print(isinstance(my_car, Vehicle))  # True
print(issubclass(Car, Vehicle))     # True
print(issubclass(Vehicle, Car))     # False

継承の設計原則

継承を使用する際には、以下の原則を考慮することが重要です。

  1. Liskovの置換原則: 子クラスは親クラスと置換可能でなければならない
  2. 単一責任の原則: 各クラスは単一の責任のみを持つべき
  3. 依存性逆転の原則: 具象クラスではなく抽象クラスに依存する

実践的な例:ゲームのキャラクターシステム

継承とポリモーフィズムの実践的な使用例として、ゲームのキャラクターシステムを実装してみましょう。このコードはGameCharacterを基底クラスとして、WarriorMageArcherがそれぞれ独自の攻撃方法を実装しています。

共通メソッド(attack, take_damage, heal, など)を共有しつつ、クラスごとに異なる挙動を示すことで、柔軟なバトルシステムを実現しています。
battle()関数では、それぞれのキャラクターが交互に攻撃し、倒れるまで戦う流れを表しています。

class GameCharacter:
    def __init__(self, name, level, health):
        self.name = name
        self.level = level
        self.health = health
        self.max_health = health

    def attack(self):
        raise NotImplementedError("サブクラスで実装してください")

    def take_damage(self, damage):
        self.health = max(0, self.health - damage)
        print(f"{self.name}は{damage}のダメージを受けた!")
        if self.health == 0:
            print(f"{self.name}は倒れた!")

    def heal(self, amount):
        self.health = min(self.max_health, self.health + amount)
        print(f"{self.name}は{amount}回復した!")

    def is_alive(self):
        return self.health > 0

    def display_status(self):
        print(f"{self.name} Lv{self.level} HP: {self.health}/{self.max_health}")

class Warrior(GameCharacter):
    def __init__(self, name, level, health, strength):
        super().__init__(name, level, health)
        self.strength = strength

    def attack(self):
        damage = self.strength + self.level
        print(f"{self.name}の剣の攻撃! {damage}のダメージ!")
        return damage

    def power_attack(self):
        damage = self.strength * 2 + self.level
        self.take_damage(5)  # 反動ダメージ
        print(f"{self.name}の強力な一撃! {damage}のダメージ!")
        return damage

class Mage(GameCharacter):
    def __init__(self, name, level, health, magic_power):
        super().__init__(name, level, health)
        self.magic_power = magic_power
        self.mana = 100

    def attack(self):
        damage = self.magic_power // 2
        print(f"{self.name}の魔法の攻撃! {damage}のダメージ!")
        return damage

    def fire_ball(self):
        if self.mana >= 20:
            damage = self.magic_power + self.level
            self.mana -= 20
            print(f"{self.name}のファイアボール! {damage}のダメージ!")
            return damage
        else:
            print("マナが足りない!")
            return 0

    def display_status(self):
        super().display_status()
        print(f"MP: {self.mana}/100")

class Archer(GameCharacter):
    def __init__(self, name, level, health, dexterity):
        super().__init__(name, level, health)
        self.dexterity = dexterity
        self.arrows = 10

    def attack(self):
        if self.arrows > 0:
            damage = self.dexterity + self.level // 2
            self.arrows -= 1
            print(f"{self.name}の弓の攻撃! {damage}のダメージ!")
            return damage
        else:
            print("矢が足りない!")
            return 0

    def multi_shot(self):
        if self.arrows >= 3:
            damage = self.dexterity * 1.5
            self.arrows -= 3
            print(f"{self.name}のマルチショット! {damage}のダメージ!")
            return damage
        else:
            print("矢が足りない!")
            return 0

    def display_status(self):
        super().display_status()
        print(f"矢: {self.arrows}本")

# ポリモーフィズムの実演
def battle(character1, character2):
    print("=== バトル開始 ===")

    while character1.is_alive() and character2.is_alive():
        # キャラクター1の攻撃
        damage = character1.attack()
        character2.take_damage(damage)

        if not character2.is_alive():
            break

        # キャラクター2の攻撃
        damage = character2.attack()
        character1.take_damage(damage)

    if character1.is_alive():
        print(f"{character1.name}の勝利!")
    else:
        print(f"{character2.name}の勝利!")

# 使用例
warrior = Warrior("勇者", 10, 100, 15)
mage = Mage("魔法使い", 8, 80, 25)

characters = [warrior, mage]

for character in characters:
    character.display_status()
    character.attack()
    print()

# バトルの実行
battle(warrior, mage)

まとめ

継承とポリモーフィズムは、オブジェクト指向プログラミングの強力な概念です。継承によりコードの再利用性と保守性が向上し、ポリモーフィズムにより柔軟で拡張性の高い設計が可能になります。

適切に使用することで、以下の利点が得られます。

  • コードの重複を減らす
  • 階層的な関係を自然に表現する
  • 新しい機能の追加が容易になる
  • 同じインターフェースで異なる振る舞いを実現する

次回は「カプセル化とアクセス修飾子」について学び、オブジェクトの内部状態を保護する方法について探求していきます。


演習問題

初級問題

問題1
次のコードを完成させて、Animalクラスを継承したDogクラスとCatクラスを作成してください。各クラスはspeakメソッドを持ち、それぞれ異なる鳴き声を出力するようにしてください。

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

問題2
Vehicleクラスを継承したCarクラスを作成してください。Carクラスはdoors属性を追加し、display_infoメソッドをオーバーライドしてドア数も表示するようにしてください。

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def display_info(self):
        print(f"{self.brand} {self.model}")

問題3
次のクラス階層で、RectangleクラスとCircleクラスにareaメソッドを実装してください。

class Shape:
    def area(self):
        pass

中級問題

問題4
Employee抽象基底クラスを作成し、それを継承したManagerクラスとDeveloperクラスを実装してください。各クラスはcalculate_bonusメソッドを実装し、ボーナス計算方法が異なるようにしてください。

問題5
BankAccountクラスを継承したSavingsAccountクラスとCheckingAccountクラスを作成してください。SavingsAccountは利息計算機能を、CheckingAccountは手数料計算機能を追加してください。

問題6
ゲームのアイテムシステムを設計してください。Item基本クラスと、それを継承したWeaponArmorPotionクラスを作成し、ポリモーフィズムを使用して各アイテムの使用効果を実装してください。

問題7
Personクラスを継承したStudentクラスとTeacherクラスを作成してください。多重継承を使用して、Researcherクラスも継承するGraduateStudentクラスを作成してください。

問題8
動物の階層構造を作成してください。MammalBirdReptileクラスを作成し、それぞれ適切なサブクラスを定義してください。各クラスはmoveメソッドを持ち、移動方法が異なるようにしてください。

問題9
電子機器のクラス階層を作成してください。ElectronicDevice基本クラスからSmartPhoneLaptopTabletクラスを派生させ、各デバイス特有の機能をメソッドとして追加してください。

上級問題

問題10
図形描画システムを設計してください。Drawable抽象クラスを定義し、PointLineRectangleCircleクラスで実装してください。ポリモーフィズムを使用してすべての図形を同じインターフェースで描画できるようにしてください。

問題11
在庫管理システムを拡張してください。Product基本クラスからPhysicalProductDigitalProductクラスを派生させ、それぞれに適した在庫管理方法を実装してください。ポリモーフィズムを使用して在庫確認を統一的なインターフェースで行えるようにしてください。

問題12
通知システムを設計してください。Notification抽象クラスを定義し、EmailNotificationSMSNotificationPushNotificationクラスで実装してください。すべての通知タイプを同じインターフェースで送信できるようにし、新しい通知タイプの追加が容易な設計にしてください。

初級問題

問題1 解答

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print(f"{self.name}はワンワン吠えます")

class Cat(Animal):
    def speak(self):
        print(f"{self.name}はニャーと鳴きます")

# 使用例
dog = Dog("ポチ")
cat = Cat("タマ")

dog.speak()  # ポチはワンワン吠えます
cat.speak()  # タマはニャーと鳴きます

解説: 親クラスのメソッドをオーバーライドして、各子クラスで異なる振る舞いを実装しています。

問題2 解答

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def display_info(self):
        print(f"{self.brand} {self.model}")

class Car(Vehicle):
    def __init__(self, brand, model, doors):
        super().__init__(brand, model)
        self.doors = doors

    def display_info(self):
        print(f"{self.brand} {self.model} - {self.doors}ドア")

# 使用例
vehicle = Vehicle("Toyota", "Camry")
car = Car("Honda", "Civic", 4)

vehicle.display_info()  # Toyota Camry
car.display_info()      # Honda Civic - 4ドア

解説: super()で親クラスのコンストラクタを呼び出し、メソッドをオーバーライドして追加情報を表示しています。

問題3 解答

class Shape:
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

# 使用例
shapes = [Rectangle(5, 3), Circle(2)]

for shape in shapes:
    print(f"面積: {shape.area():.2f}")

# 出力:
# 面積: 15.00
# 面積: 12.57

解説: ポリモーフィズムを活用して、異なる図形オブジェクトを同じインターフェースで扱っています。

中級問題

問題4 解答

from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    @abstractmethod
    def calculate_bonus(self):
        pass

    def display_info(self):
        print(f"{self.name} - 基本給: {self.salary}円")

class Manager(Employee):
    def calculate_bonus(self):
        return self.salary * 0.3  # 基本給の30%

    def display_info(self):
        super().display_info()
        print(f"ボーナス: {self.calculate_bonus()}円")

class Developer(Employee):
    def __init__(self, name, salary, projects_completed):
        super().__init__(name, salary)
        self.projects_completed = projects_completed

    def calculate_bonus(self):
        return self.salary * 0.2 + self.projects_completed * 5000

    def display_info(self):
        super().display_info()
        print(f"完了プロジェクト: {self.projects_completed}")
        print(f"ボーナス: {self.calculate_bonus()}円")

# 使用例
employees = [
    Manager("山田部長", 800000),
    Developer("佐藤エンジニア", 600000, 5)
]

for employee in employees:
    employee.display_info()
    print()

問題5 解答

class BankAccount:
    def __init__(self, account_number, balance=0):
        self.account_number = account_number
        self.balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"{amount}円預け入れました")
        return self.balance

    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            print(f"{amount}円引き出しました")
        return self.balance

    def display_balance(self):
        print(f"口座番号: {self.account_number}")
        print(f"残高: {self.balance}円")

class SavingsAccount(BankAccount):
    def __init__(self, account_number, balance=0, interest_rate=0.02):
        super().__init__(account_number, balance)
        self.interest_rate = interest_rate

    def calculate_interest(self, months=1):
        interest = self.balance * self.interest_rate * months / 12
        print(f"{months}ヶ月分の利息: {interest:.2f}円")
        return interest

    def apply_interest(self, months=1):
        interest = self.calculate_interest(months)
        self.balance += interest
        print(f"利息を適用しました")

class CheckingAccount(BankAccount):
    def __init__(self, account_number, balance=0, transaction_fee=100):
        super().__init__(account_number, balance)
        self.transaction_fee = transaction_fee
        self.transaction_count = 0

    def withdraw(self, amount):
        result = super().withdraw(amount)
        self.transaction_count += 1
        if self.transaction_count > 3:
            self.balance -= self.transaction_fee
            print(f"取引手数料: {self.transaction_fee}円")
        return result

    def reset_transaction_count(self):
        self.transaction_count = 0
        print("取引回数をリセットしました")

# 使用例
savings = SavingsAccount("SAV001", 100000, 0.03)
checking = CheckingAccount("CHK001", 50000, 150)

print("=== 普通預金口座 ===")
savings.deposit(50000)
savings.calculate_interest(6)
savings.apply_interest()
savings.display_balance()

print("\n=== 当座預金口座 ===")
for _ in range(5):
    checking.withdraw(1000)
checking.display_balance()

問題6 解答

from abc import ABC, abstractmethod

class Item(ABC):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    @abstractmethod
    def use(self, character):
        pass

    def __str__(self):
        return f"{self.name} (価値: {self.value})"

class Weapon(Item):
    def __init__(self, name, value, attack_power):
        super().__init__(name, value)
        self.attack_power = attack_power

    def use(self, character):
        print(f"{self.name}で攻撃! {self.attack_power}のダメージ!")
        return self.attack_power

class Armor(Item):
    def __init__(self, name, value, defense_power):
        super().__init__(name, value)
        self.defense_power = defense_power

    def use(self, character):
        print(f"{self.name}を装備! 防御力+{self.defense_power}")
        return self.defense_power

class Potion(Item):
    def __init__(self, name, value, heal_amount):
        super().__init__(name, value)
        self.heal_amount = heal_amount

    def use(self, character):
        print(f"{self.name}を使用! HPを{self.heal_amount}回復!")
        return self.heal_amount

class GameCharacter:
    def __init__(self, name, health=100):
        self.name = name
        self.health = health
        self.inventory = []

    def use_item(self, item_index):
        if 0 <= item_index < len(self.inventory):
            item = self.inventory[item_index]
            result = item.use(self)
            if isinstance(item, Potion):
                self.health += result
            return result
        return 0

    def add_item(self, item):
        self.inventory.append(item)
        print(f"{item.name}を入手しました")

# 使用例
character = GameCharacter("勇者")

# アイテムの追加
character.add_item(Weapon("鋼の剣", 100, 15))
character.add_item(Armor("鉄の鎧", 150, 10))
character.add_item(Potion("回復薬", 50, 30))

# ポリモーフィズムの実演
print("=== アイテム使用 ===")
for i in range(len(character.inventory)):
    character.use_item(i)
    print()

問題7 解答

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"私は{self.name}です。{self.age}歳です。")

class Student(Person):
    def __init__(self, name, age, student_id):
        super().__init__(name, age)
        self.student_id = student_id
        self.courses = []

    def enroll_course(self, course):
        self.courses.append(course)
        print(f"{course}を履修登録しました")

    def introduce(self):
        super().introduce()
        print(f"学籍番号: {self.student_id}")

class Teacher(Person):
    def __init__(self, name, age, subject):
        super().__init__(name, age)
        self.subject = subject

    def teach(self):
        print(f"{self.subject}を教えています")

    def introduce(self):
        super().introduce()
        print(f"担当科目: {self.subject}")

class Researcher:
    def __init__(self, research_field):
        self.research_field = research_field
        self.publications = []

    def add_publication(self, publication):
        self.publications.append(publication)
        print(f"論文 '{publication}' を追加しました")

    def conduct_research(self):
        print(f"{self.research_field}の研究を行っています")

class GraduateStudent(Student, Researcher):
    def __init__(self, name, age, student_id, research_field):
        Student.__init__(self, name, age, student_id)
        Researcher.__init__(self, research_field)

    def introduce(self):
        super().introduce()
        print(f"研究分野: {self.research_field}")
        print(f"論文数: {len(self.publications)}")

# 使用例
grad_student = GraduateStudent("鈴木花子", 25, "G12345", "人工知能")
grad_student.introduce()
grad_student.enroll_course("機械学習")
grad_student.add_publication("深層学習の応用")
grad_student.conduct_research()

問題8 解答

from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name, habitat):
        self.name = name
        self.habitat = habitat

    @abstractmethod
    def move(self):
        pass

    def display_info(self):
        print(f"{self.name} - 生息地: {self.habitat}")

class Mammal(Animal):
    def __init__(self, name, habitat, fur_color):
        super().__init__(name, habitat)
        self.fur_color = fur_color

    def move(self):
        print(f"{self.name}は走ります")

    def display_info(self):
        super().display_info()
        print(f"毛色: {self.fur_color}")

class Bird(Animal):
    def __init__(self, name, habitat, wingspan):
        super().__init__(name, habitat)
        self.wingspan = wingspan

    def move(self):
        print(f"{self.name}は飛びます")

    def display_info(self):
        super().display_info()
        print(f"翼幅: {self.wingspan}cm")

class Reptile(Animal):
    def __init__(self, name, habitat, scale_type):
        super().__init__(name, habitat)
        self.scale_type = scale_type

    def move(self):
        print(f"{self.name}は這います")

    def display_info(self):
        super().display_info()
        print(f"鱗の種類: {self.scale_type}")

# 哺乳類のサブクラス
class Lion(Mammal):
    def __init__(self, name, habitat, fur_color, mane_size):
        super().__init__(name, habitat, fur_color)
        self.mane_size = mane_size

    def move(self):
        print(f"{self.name}は悠然と歩きます")

    def roar(self):
        print("ガオー!")

# 鳥類のサブクラス
class Eagle(Bird):
    def __init__(self, name, habitat, wingspan, vision_range):
        super().__init__(name, habitat, wingspan)
        self.vision_range = vision_range

    def move(self):
        print(f"{self.name}は高く舞い上がります")

    def hunt(self):
        print("獲物を探しています")

# 爬虫類のサブクラス
class Snake(Reptile):
    def __init__(self, name, habitat, scale_type, length):
        super().__init__(name, habitat, scale_type)
        self.length = length

    def move(self):
        print(f"{self.name}は静かに這います")

    def shed_skin(self):
        print("脱皮しました")

# 使用例
animals = [
    Lion("シンバ", "サバンナ", "金色", "大きい"),
    Eagle("ホーク", "山岳地帯", 200, "2km"),
    Snake("ナーガ", "ジャングル", "光沢のある", 3.0)
]

for animal in animals:
    animal.display_info()
    animal.move()
    print()

問題9 解答

class ElectronicDevice:
    def __init__(self, brand, model, power_consumption):
        self.brand = brand
        self.model = model
        self.power_consumption = power_consumption
        self.is_on = False

    def turn_on(self):
        self.is_on = True
        print(f"{self.brand} {self.model}の電源を入れました")

    def turn_off(self):
        self.is_on = False
        print(f"{self.brand} {self.model}の電源を切りました")

    def get_power_usage(self, hours):
        return self.power_consumption * hours

    def display_info(self):
        status = "ON" if self.is_on else "OFF"
        print(f"{self.brand} {self.model} - 状態: {status}")

class SmartPhone(ElectronicDevice):
    def __init__(self, brand, model, power_consumption, os, screen_size):
        super().__init__(brand, model, power_consumption)
        self.os = os
        self.screen_size = screen_size
        self.apps = []

    def make_call(self, number):
        if self.is_on:
            print(f"{number}に電話をかけます")
        else:
            print("電源が入っていません")

    def install_app(self, app_name):
        self.apps.append(app_name)
        print(f"アプリ '{app_name}' をインストールしました")

    def display_info(self):
        super().display_info()
        print(f"OS: {self.os}, 画面サイズ: {self.screen_size}インチ")
        print(f"インストールアプリ数: {len(self.apps)}")

class Laptop(ElectronicDevice):
    def __init__(self, brand, model, power_consumption, processor, ram):
        super().__init__(brand, model, power_consumption)
        self.processor = processor
        self.ram = ram
        self.battery_level = 100

    def code(self):
        if self.is_on:
            print("コーディング中...")
            self.battery_level -= 5
        else:
            print("電源が入っていません")

    def check_battery(self):
        print(f"バッテリー残量: {self.battery_level}%")

    def display_info(self):
        super().display_info()
        print(f"プロセッサ: {self.processor}, RAM: {self.ram}GB")

class Tablet(ElectronicDevice):
    def __init__(self, brand, model, power_consumption, stylus_support, storage):
        super().__init__(brand, model, power_consumption)
        self.stylus_support = stylus_support
        self.storage = storage
        self.drawing_apps = []

    def draw(self):
        if self.is_on and self.stylus_support:
            print("絵を描いています")
        elif not self.stylus_support:
            print("このタブレットはスタイラス非対応です")
        else:
            print("電源が入っていません")

    def install_drawing_app(self, app_name):
        self.drawing_apps.append(app_name)
        print(f"描画アプリ '{app_name}' をインストールしました")

    def display_info(self):
        super().display_info()
        support = "対応" if self.stylus_support else "非対応"
        print(f"スタイラス: {support}, ストレージ: {self.storage}GB")

# 使用例
devices = [
    SmartPhone("Apple", "iPhone 15", 5, "iOS", 6.1),
    Laptop("Dell", "XPS 13", 45, "Intel i7", 16),
    Tablet("Samsung", "Galaxy Tab", 8, True, 128)
]

for device in devices:
    device.turn_on()
    device.display_info()

    # デバイス特有の機能
    if isinstance(device, SmartPhone):
        device.make_call("090-1234-5678")
        device.install_app("LINE")
    elif isinstance(device, Laptop):
        device.code()
        device.check_battery()
    elif isinstance(device, Tablet):
        device.draw()
        device.install_drawing_app("Procreate")

    print()

上級問題

問題10 解答

from abc import ABC, abstractmethod
import math

class Drawable(ABC):
    @abstractmethod
    def draw(self):
        pass

    @abstractmethod
    def get_area(self):
        pass

    @abstractmethod
    def get_perimeter(self):
        pass

class Point(Drawable):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self):
        print(f"点を描画: ({self.x}, {self.y})")

    def get_area(self):
        return 0

    def get_perimeter(self):
        return 0

    def distance_to(self, other_point):
        return math.sqrt((self.x - other_point.x)**2 + (self.y - other_point.y)**2)

class Line(Drawable):
    def __init__(self, start_point, end_point):
        self.start_point = start_point
        self.end_point = end_point

    def draw(self):
        print(f"線を描画: ({self.start_point.x}, {self.start_point.y}) -> ({self.end_point.x}, {self.end_point.y})")

    def get_area(self):
        return 0

    def get_perimeter(self):
        return self.start_point.distance_to(self.end_point)

    def get_length(self):
        return self.get_perimeter()

class Rectangle(Drawable):
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def draw(self):
        print(f"長方形を描画: 位置({self.x}, {self.y}), サイズ({self.width}×{self.height})")

    def get_area(self):
        return self.width * self.height

    def get_perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Drawable):
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius

    def draw(self):
        print(f"円を描画: 中心({self.x}, {self.y}), 半径{self.radius}")

    def get_area(self):
        return math.pi * self.radius ** 2

    def get_perimeter(self):
        return 2 * math.pi * self.radius

class Drawing:
    def __init__(self):
        self.shapes = []

    def add_shape(self, shape):
        self.shapes.append(shape)
        print("図形を追加しました")

    def draw_all(self):
        print("=== すべての図形を描画 ===")
        for shape in self.shapes:
            shape.draw()
            print(f"  面積: {shape.get_area():.2f}, 周囲長: {shape.get_perimeter():.2f}")
        print("========================")

    def total_area(self):
        return sum(shape.get_area() for shape in self.shapes)

    def total_perimeter(self):
        return sum(shape.get_perimeter() for shape in self.shapes)

# 使用例
drawing = Drawing()

# 様々な図形を追加
drawing.add_shape(Point(0, 0))
drawing.add_shape(Line(Point(0, 0), Point(3, 4)))
drawing.add_shape(Rectangle(1, 1, 5, 3))
drawing.add_shape(Circle(2, 2, 3))

# ポリモーフィズムで一括描画
drawing.draw_all()

print(f"総面積: {drawing.total_area():.2f}")
print(f"総周囲長: {drawing.total_perimeter():.2f}")

問題11 解答

from abc import ABC, abstractmethod
from datetime import datetime

class Product(ABC):
    def __init__(self, product_id, name, price):
        self.product_id = product_id
        self.name = name
        self.price = price
        self.created_at = datetime.now()

    @abstractmethod
    def check_inventory(self):
        pass

    @abstractmethod
    def update_inventory(self, quantity):
        pass

    @abstractmethod
    def can_sell(self, quantity):
        pass

    def display_info(self):
        print(f"ID: {self.product_id}")
        print(f"商品名: {self.name}")
        print(f"価格: {self.price}円")

class PhysicalProduct(Product):
    def __init__(self, product_id, name, price, stock_quantity, weight):
        super().__init__(product_id, name, price)
        self.stock_quantity = stock_quantity
        self.weight = weight

    def check_inventory(self):
        return self.stock_quantity

    def update_inventory(self, quantity):
        if self.stock_quantity + quantity >= 0:
            self.stock_quantity += quantity
            return True
        return False

    def can_sell(self, quantity):
        return self.stock_quantity >= quantity

    def calculate_shipping_cost(self, distance):
        return self.weight * distance * 0.1

    def display_info(self):
        super().display_info()
        print(f"在庫数: {self.stock_quantity}")
        print(f"重量: {self.weight}kg")

class DigitalProduct(Product):
    def __init__(self, product_id, name, price, file_size, download_limit=None):
        super().__init__(product_id, name, price)
        self.file_size = file_size
        self.download_limit = download_limit
        self.download_count = 0

    def check_inventory(self):
        if self.download_limit is None:
            return "無制限"
        return f"{self.download_limit - self.download_count}回"

    def update_inventory(self, quantity):
        if self.download_limit is None:
            return True

        if self.download_count + quantity <= self.download_limit:
            self.download_count += quantity
            return True
        return False

    def can_sell(self, quantity):
        if self.download_limit is None:
            return True
        return self.download_count + quantity <= self.download_limit

    def get_file_info(self):
        return f"ファイルサイズ: {self.file_size}MB, ダウンロード回数: {self.download_count}"

    def display_info(self):
        super().display_info()
        print(f"ファイルサイズ: {self.file_size}MB")
        if self.download_limit:
            print(f"ダウンロード制限: {self.download_limit}回")
        print(f"現在のダウンロード回数: {self.download_count}")

class InventoryManager:
    def __init__(self):
        self.products = []

    def add_product(self, product):
        self.products.append(product)
        print(f"商品 '{product.name}' を追加しました")

    def sell_product(self, product_id, quantity):
        for product in self.products:
            if product.product_id == product_id and product.can_sell(quantity):
                product.update_inventory(quantity)
                total_price = product.price * quantity
                print(f"商品 '{product.name}' {quantity}個を販売しました")
                print(f"売上: {total_price}円")

                # 物理商品の場合、配送コストを計算
                if isinstance(product, PhysicalProduct):
                    shipping_cost = product.calculate_shipping_cost(100)  # 仮の距離
                    print(f"配送コスト: {shipping_cost}円")

                return True
        print("販売できませんでした")
        return False

    def display_inventory_report(self):
        print("\n=== 在庫レポート ===")
        for product in self.products:
            product.display_info()
            print(f"在庫状態: {product.check_inventory()}")
            print("---")
        print("===================")

# 使用例
inventory_manager = InventoryManager()

# 商品の追加
physical_product = PhysicalProduct("P001", "ノートパソコン", 150000, 10, 2.5)
digital_product = DigitalProduct("D001", "電子書籍", 2000, 50, 3)

inventory_manager.add_product(physical_product)
inventory_manager.add_product(digital_product)

# 在庫レポート表示
inventory_manager.display_inventory_report()

# 商品販売
print("\n=== 販売処理 ===")
inventory_manager.sell_product("P001", 2)
inventory_manager.sell_product("D001", 1)

# 最終レポート
inventory_manager.display_inventory_report()

問題12 解答

from abc import ABC, abstractmethod
from datetime import datetime

class Notification(ABC):
    def __init__(self, recipient, message):
        self.recipient = recipient
        self.message = message
        self.sent_at = None
        self.status = "準備中"

    @abstractmethod
    def send(self):
        pass

    @abstractmethod
    def get_notification_type(self):
        pass

    def mark_sent(self):
        self.sent_at = datetime.now()
        self.status = "送信済み"

    def get_status(self):
        return f"{self.get_notification_type()} - {self.status}"

    def display_info(self):
        print(f"通知タイプ: {self.get_notification_type()}")
        print(f"受信者: {self.recipient}")
        print(f"メッセージ: {self.message}")
        print(f"ステータス: {self.status}")
        if self.sent_at:
            print(f"送信日時: {self.sent_at.strftime('%Y-%m-%d %H:%M:%S')}")

class EmailNotification(Notification):
    def __init__(self, recipient, message, subject, from_address):
        super().__init__(recipient, message)
        self.subject = subject
        self.from_address = from_address

    def send(self):
        print(f"メール送信: {self.from_address} -> {self.recipient}")
        print(f"件名: {self.subject}")
        print(f"本文: {self.message}")
        self.mark_sent()
        return True

    def get_notification_type(self):
        return "メール通知"

    def display_info(self):
        super().display_info()
        print(f"差出人: {self.from_address}")
        print(f"件名: {self.subject}")

class SMSNotification(Notification):
    def __init__(self, recipient, message, phone_number):
        super().__init__(recipient, message)
        self.phone_number = phone_number

    def send(self):
        print(f"SMS送信: {self.phone_number}")
        print(f"メッセージ: {self.message}")
        self.mark_sent()
        return True

    def get_notification_type(self):
        return "SMS通知"

    def display_info(self):
        super().display_info()
        print(f"電話番号: {self.phone_number}")

class PushNotification(Notification):
    def __init__(self, recipient, message, device_token, app_name):
        super().__init__(recipient, message)
        self.device_token = device_token
        self.app_name = app_name

    def send(self):
        print(f"プッシュ通知送信: {self.app_name}")
        print(f"デバイス: {self.device_token}")
        print(f"メッセージ: {self.message}")
        self.mark_sent()
        return True

    def get_notification_type(self):
        return "プッシュ通知"

    def display_info(self):
        super().display_info()
        print(f"アプリ名: {self.app_name}")
        print(f"デバイストークン: {self.device_token}")

class NotificationManager:
    def __init__(self):
        self.notifications = []
        self.sent_count = 0

    def add_notification(self, notification):
        self.notifications.append(notification)
        print(f"通知を追加しました: {notification.get_notification_type()}")

    def send_all(self):
        print("\n=== 一括通知送信 ===")
        success_count = 0
        for notification in self.notifications:
            if notification.status != "送信済み":
                if notification.send():
                    success_count += 1
                print("---")
        self.sent_count += success_count
        print(f"{success_count}件の通知を送信しました")

    def send_by_type(self, notification_type):
        print(f"\n=== {notification_type}のみ送信 ===")
        count = 0
        for notification in self.notifications:
            if (notification.get_notification_type() == notification_type and 
                notification.status != "送信済み"):
                if notification.send():
                    count += 1
                print("---")
        print(f"{count}件の{notification_type}を送信しました")

    def display_statistics(self):
        print("\n=== 通知統計 ===")
        type_count = {}
        for notification in self.notifications:
            n_type = notification.get_notification_type()
            type_count[n_type] = type_count.get(n_type, 0) + 1

        for n_type, count in type_count.items():
            print(f"{n_type}: {count}件")
        print(f"総送信済み数: {self.sent_count}件")
        print("================")

    def display_all_notifications(self):
        print("\n=== すべての通知 ===")
        for i, notification in enumerate(self.notifications, 1):
            print(f"{i}. ", end="")
            notification.display_info()
            print()

# 使用例
notification_manager = NotificationManager()

# 様々なタイプの通知を作成
email_notif = EmailNotification(
    "user@example.com", 
    "お知らせがあります", 
    "重要なお知らせ", 
    "noreply@company.com"
)

sms_notif = SMSNotification(
    "山田太郎", 
    "本日15時にお伺いします", 
    "090-1234-5678"
)

push_notif = PushNotification(
    "user123", 
    "新しいメッセージが届きました", 
    "device_token_abc123", 
    "MyApp"
)

# 通知をマネージャーに追加
notification_manager.add_notification(email_notif)
notification_manager.add_notification(sms_notif)
notification_manager.add_notification(push_notif)

# 通知の表示
notification_manager.display_all_notifications()

# 統計表示
notification_manager.display_statistics()

# 通知送信
notification_manager.send_all()

# 最終統計
notification_manager.display_statistics()