Documentation Python

Pythonの抽象基底クラス(ABC: Abstract Base Class)は、クラス設計において、他のクラスに共通のインターフェースや構造を提供するためのクラスです。この記事では、abcモジュールを使った抽象基底クラスの基本的な使い方から応用まで解説します。

abcモジュールの主要機能

機能説明使用例
ABC抽象基底クラスを定義するための基底クラスclass MyABC(ABC)
@abstractmethod抽象メソッドを定義するデコレータサブクラスで実装必須
@abstractproperty抽象プロパティを定義(非推奨)@property + @abstractmethod
register()クラスを仮想サブクラスとして登録MyABC.register(OtherClass)
__subclasshook__isinstance/issubclassの動作をカスタマイズダックタイピング的な判定

抽象基底クラスとは

抽象基底クラスは直接インスタンス化することはできず、サブクラスで具体的な実装を行うことを前提としています。

from abc import ABC, abstractmethod

# 抽象基底クラスは直接インスタンス化できない
class Animal(ABC):
    @abstractmethod
    def make_sound(self):
        pass

# エラー: 抽象メソッドが実装されていない
try:
    animal = Animal()
except TypeError as e:
    print(f"エラー: {e}")
    # 出力: エラー: Can't instantiate abstract class Animal with abstract method make_sound

基本的な使い方

抽象基底クラスの定義

ABCクラスを継承し、@abstractmethodデコレータを使って抽象メソッドを指定します。

from abc import ABC, abstractmethod

class Shape(ABC):
    """図形の抽象基底クラス"""

    @abstractmethod
    def area(self) -> float:
        """面積を計算する"""
        pass

    @abstractmethod
    def perimeter(self) -> float:
        """周囲長を計算する"""
        pass

    # 具象メソッド(共通処理)
    def describe(self) -> str:
        return f"面積: {self.area():.2f}, 周囲長: {self.perimeter():.2f}"

class Rectangle(Shape):
    """長方形クラス"""

    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

class Circle(Shape):
    """円クラス"""

    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        import math
        return math.pi * self.radius ** 2

    def perimeter(self) -> float:
        import math
        return 2 * math.pi * self.radius

# 使用例
rect = Rectangle(5, 3)
circle = Circle(4)

print(rect.describe())    # 出力: 面積: 15.00, 周囲長: 16.00
print(circle.describe())  # 出力: 面積: 50.27, 周囲長: 25.13

抽象プロパティ

@property@abstractmethodを組み合わせて、抽象プロパティを定義できます。

from abc import ABC, abstractmethod

class Vehicle(ABC):
    """乗り物の抽象基底クラス"""

    @property
    @abstractmethod
    def max_speed(self) -> int:
        """最高速度(km/h)"""
        pass

    @property
    @abstractmethod
    def fuel_type(self) -> str:
        """燃料の種類"""
        pass

    def info(self) -> str:
        return f"最高速度: {self.max_speed}km/h, 燃料: {self.fuel_type}"

class Car(Vehicle):
    @property
    def max_speed(self) -> int:
        return 180

    @property
    def fuel_type(self) -> str:
        return "ガソリン"

class ElectricBike(Vehicle):
    @property
    def max_speed(self) -> int:
        return 45

    @property
    def fuel_type(self) -> str:
        return "電気"

car = Car()
bike = ElectricBike()

print(car.info())   # 出力: 最高速度: 180km/h, 燃料: ガソリン
print(bike.info())  # 出力: 最高速度: 45km/h, 燃料: 電気

実践的な例

支払い処理システム

異なる支払い方法を統一的に扱うシステムです。

from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional

class PaymentProcessor(ABC):
    """支払い処理の抽象基底クラス"""

    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        """支払いを処理する"""
        pass

    @abstractmethod
    def refund(self, amount: float, transaction_id: str) -> bool:
        """返金処理を行う"""
        pass

    @abstractmethod
    def get_transaction_fee(self, amount: float) -> float:
        """手数料を計算する"""
        pass

    def validate_amount(self, amount: float) -> bool:
        """金額の妥当性を検証(共通処理)"""
        return amount > 0

class CreditCardProcessor(PaymentProcessor):
    """クレジットカード決済"""

    FEE_RATE = 0.03  # 3%の手数料

    def process_payment(self, amount: float) -> bool:
        if not self.validate_amount(amount):
            return False
        fee = self.get_transaction_fee(amount)
        print(f"クレジットカード決済: {amount}円 (手数料: {fee}円)")
        return True

    def refund(self, amount: float, transaction_id: str) -> bool:
        print(f"クレジットカード返金: {amount}円 (取引ID: {transaction_id})")
        return True

    def get_transaction_fee(self, amount: float) -> float:
        return amount * self.FEE_RATE

class PayPayProcessor(PaymentProcessor):
    """PayPay決済"""

    FEE_RATE = 0.02  # 2%の手数料

    def process_payment(self, amount: float) -> bool:
        if not self.validate_amount(amount):
            return False
        fee = self.get_transaction_fee(amount)
        print(f"PayPay決済: {amount}円 (手数料: {fee}円)")
        return True

    def refund(self, amount: float, transaction_id: str) -> bool:
        print(f"PayPay返金: {amount}円 (取引ID: {transaction_id})")
        return True

    def get_transaction_fee(self, amount: float) -> float:
        return amount * self.FEE_RATE

# 支払い処理を統一的に扱う
def checkout(processor: PaymentProcessor, amount: float):
    """統一された決済インターフェース"""
    fee = processor.get_transaction_fee(amount)
    total = amount + fee
    print(f"合計: {total}円")
    return processor.process_payment(amount)

# 使用例
credit_card = CreditCardProcessor()
paypay = PayPayProcessor()

checkout(credit_card, 10000)  # クレジットカードで決済
checkout(paypay, 5000)        # PayPayで決済

データアクセス層(リポジトリパターン)

データベース操作を抽象化する例です。

from abc import ABC, abstractmethod
from typing import TypeVar, Generic, Optional, List

T = TypeVar('T')

class Repository(ABC, Generic[T]):
    """リポジトリの抽象基底クラス"""

    @abstractmethod
    def find_by_id(self, id: int) -> Optional[T]:
        """IDで検索"""
        pass

    @abstractmethod
    def find_all(self) -> List[T]:
        """全件取得"""
        pass

    @abstractmethod
    def save(self, entity: T) -> T:
        """保存"""
        pass

    @abstractmethod
    def delete(self, id: int) -> bool:
        """削除"""
        pass

class User:
    def __init__(self, id: int, name: str, email: str):
        self.id = id
        self.name = name
        self.email = email

    def __repr__(self):
        return f"User(id={self.id}, name='{self.name}')"

class InMemoryUserRepository(Repository[User]):
    """メモリ上で動作するユーザーリポジトリ(テスト用)"""

    def __init__(self):
        self._users: dict[int, User] = {}
        self._next_id = 1

    def find_by_id(self, id: int) -> Optional[User]:
        return self._users.get(id)

    def find_all(self) -> List[User]:
        return list(self._users.values())

    def save(self, entity: User) -> User:
        if entity.id == 0:
            entity.id = self._next_id
            self._next_id += 1
        self._users[entity.id] = entity
        return entity

    def delete(self, id: int) -> bool:
        if id in self._users:
            del self._users[id]
            return True
        return False

# 使用例
repo = InMemoryUserRepository()

# ユーザーを保存
user1 = repo.save(User(0, "田中太郎", "tanaka@example.com"))
user2 = repo.save(User(0, "鈴木花子", "suzuki@example.com"))

print(repo.find_all())          # [User(id=1, name='田中太郎'), User(id=2, name='鈴木花子')]
print(repo.find_by_id(1))       # User(id=1, name='田中太郎')
print(repo.delete(1))           # True
print(repo.find_all())          # [User(id=2, name='鈴木花子')]

高度な機能

registerメソッド

register()を使うと、クラスを継承せずに仮想サブクラスとして登録できます。

from abc import ABC, abstractmethod

class Printable(ABC):
    @abstractmethod
    def print_content(self):
        pass

# 既存のクラス(Printableを継承していない)
class LegacyDocument:
    def print_content(self):
        return "Legacy document content"

# 仮想サブクラスとして登録
Printable.register(LegacyDocument)

# isinstanceが True を返す
doc = LegacyDocument()
print(isinstance(doc, Printable))  # 出力: True
print(issubclass(LegacyDocument, Printable))  # 出力: True

subclasshook

__subclasshook__を使うと、ダックタイピング的な判定が可能です。

from abc import ABC, abstractmethod

class Iterable(ABC):
    @abstractmethod
    def __iter__(self):
        pass

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            # __iter__メソッドを持っていればサブクラスと判定
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

# 明示的に継承していないが、__iter__を持つクラス
class MyCollection:
    def __init__(self, items):
        self._items = items

    def __iter__(self):
        return iter(self._items)

# issubclassが True を返す
print(issubclass(MyCollection, Iterable))  # 出力: True
print(isinstance(MyCollection([1, 2, 3]), Iterable))  # 出力: True

抽象基底クラスを使う利点

利点説明
インターフェースの強制サブクラスに必要なメソッドの実装を強制できる
設計の明確化クラスの責務と契約が明示される
型チェックの活用isinstance()issubclass()で型を確認できる
再利用性の向上共通処理を基底クラスで定義できる
IDE補完の向上型ヒントと組み合わせてコード補完が効く

よくある間違いと注意点

抽象メソッドを実装しないとエラー

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

# NG: 抽象メソッドを実装していない
class Dog(Animal):
    pass

# TypeError: Can't instantiate abstract class Dog with abstract method speak
# dog = Dog()

# OK: 抽象メソッドを実装
class Cat(Animal):
    def speak(self):
        return "ニャー"

cat = Cat()
print(cat.speak())  # 出力: ニャー

過度な使用は避ける

# NG: 単純なケースに抽象基底クラスは不要
class Greeter(ABC):
    @abstractmethod
    def greet(self):
        pass

# OK: シンプルなケースは普通のクラスで十分
class SimpleGreeter:
    def greet(self, name: str) -> str:
        return f"Hello, {name}!"

まとめ

用途推奨パターン
インターフェース定義ABC + @abstractmethod
抽象プロパティ@property + @abstractmethod
既存クラスの統合register()
ダックタイピング判定__subclasshook__

Pythonのabcモジュールを使った抽象基底クラスは、クラス設計を強化し、一貫したインターフェースを提供するための強力なツールです。特に大規模なプロジェクトやチーム開発において、クラス間の契約を明確にすることでコードの品質と保守性が向上します。

参考文献

円