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