<一覧に戻る

抽象クラスと抽象メソッド

「同じ名前のメソッドを持つクラスをきれいにそろえたい」「使う人に“このメソッドは必ず作ってね”と約束したい」。 そんなときに役立つのが、Pythonの抽象クラス(Abstract Base Class: ABC)と抽象メソッドです。

難しそうに聞こえるかもしれませんが、考え方はシンプルです。抽象クラスは“設計図”、抽象メソッドは“必ず実装してほしい項目”だと思ってください。

  • 抽象クラスは「インスタンス化できないクラス」です。サブクラス(子クラス)のための土台になります。
  • 抽象メソッドは「中身(処理)を持たないメソッド」で、サブクラス側で必ず実装しなければいけません。

「なぜわざわざそんな回り道を?」と思うかもしれません。

理由は2つ。
1つ目は、実装の抜け漏れを防ぐこと。 2つ目は、クラス間で共通のインターフェース(同じメソッド名・同じ使い方)をそろえられることです。

結果として、読みやすく、差し替えやすく、テストしやすいコードになります。

今回は抽象クラスと抽象メソッドについて詳しく解説します。

抽象クラスとは?

Pythonではabcモジュールを使って抽象クラスを作ります。

目印としてABCを継承し、抽象メソッドには@abstractmethodデコレータを付けます。

なお、Pythonでは「抽象メソッドが残っているクラスはインスタンス化できない」という仕様です。 逆にいうと、ABCを継承していても抽象メソッドがなければ、通常のクラスとしてインスタンス化できます。

「インターフェースとしての約束を作りたい」「継承先に必ず実装してほしいメソッドがある」――そんなときに抽象クラスを選びましょう。

サンプルコード(図形クラス)

以下は、図形の「面積」と「周囲の長さ」を計算する設計です。 共通の約束を Shape 抽象クラスが定め、具体的な計算は Circle(円)と Rectangle(長方形)が担当します。

from abc import ABC, abstractmethod

# 抽象クラスの定義
class Shape(ABC):

    @abstractmethod
    def area(self):
        """面積を計算するメソッド"""
        pass

    @abstractmethod
    def perimeter(self):
        """周囲の長さを計算するメソッド"""
        pass

# CircleクラスはShapeクラスを継承
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

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

    def perimeter(self):
        return 2 * 3.14 * self.radius

# RectangleクラスはShapeクラスを継承
class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

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

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

# インスタンスを作成
circle = Circle(5)
rectangle = Rectangle(4, 6)

# 面積と周囲の長さを表示
print(f"Circle Area: {circle.area()}")
print(f"Circle Perimeter: {circle.perimeter()}")

print(f"Rectangle Area: {rectangle.area()}")
print(f"Rectangle Perimeter: {rectangle.perimeter()}")

コードの流れと考え方

まず、Shapeは“図形なら必ず持つべき操作”を定義しています。ここではarea()perimeter()がそれにあたります。 どちらも@abstractmethodが付いているため、Shape自身はインスタンス化できません。「図形は面積と周囲の長さを計算できるべし」という約束だけを宣言している、まさに設計図の役目です。

次に、CircleRectangleがその約束を具体化します。Circleは半径から円の面積と周長を計算し、Rectangleは幅と高さから値を出します。どちらのクラスも、area()perimeter()という“同じ名前・同じ意味”のメソッドを備えているため、呼び出し側はクラスの種類を意識せずに同じ書き方で扱えます。この「同じメソッド名で、違う中身が動く」性質はポリモーフィズム(多態性)と呼ばれ、オブジェクト指向ではとても重要です。

試しに、Shape()を直接インスタンス化しようとするとどうなるでしょう?Pythonは「抽象メソッドが未実装だよ」と教えてくれます。これにより、必要なメソッドの作り忘れを早期に検知できます。

実行時のイメージとエラーチェック

「本当にインスタンス化できないの?」と気になったら、次の短い実験をしてみましょう。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    @abstractmethod
    def perimeter(self):
        pass

try:
    s = Shape()  # ここでエラー
except TypeError as e:
    print(type(e).__name__, ":", e)

また、サブクラスで実装を一つでも忘れると、やはりインスタンス化の時点でエラーになります。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    @abstractmethod
    def perimeter(self):
        pass

class BadCircle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14 * self.r ** 2
    # perimeter を実装し忘れ!

try:
    bad = BadCircle(3)  # ここで TypeError
except TypeError as e:
    print(type(e).__name__, ":", e)

この動作は、抽象クラスが「実装の抜け漏れ」を自動で見張ってくれることを意味します。安心ですね。

いつ使う?なぜ効く?

抽象クラスは必ずこのメソッドを実装してくださいという約束を強制できるため、チーム開発や大きめのプロジェクトで特に威力を発揮します。

「それぞれ自由に実装していいよ」だけだと、開発者ごとにメソッド名や呼び出し方がバラバラになりがちです。 そうなると、呼び出し側のコードがクラスごとに条件分岐だらけになってしまい、保守が難しくなります。

そこで抽象クラスを使うと、インターフェースを揃えつつ抜け漏れを防止できます。 実際に役立つのは次のような場面です。

  • 外部サービスのラッパー(例:支払い処理、ストレージ):connect(), send(), close()といった共通メソッドを強制したいとき。
  • UIコンポーネントやゲームのエンティティ:update(), render()のようにフレームごとの処理を統一したいとき。
  • 解析パイプライン:load(), transform(), save()を必ず用意する設計にしたいとき。

こうした「共通のインターフェース」を揃えると、差し替えがスムーズになります。たとえば、CircleからRectangleに変えても、呼び出し側のshape.area()という書き方は変わりません。テストコードも再利用しやすくなります。

ポリモーフィズムやプロトコルとの関係

抽象クラスは「同じメソッド名で異なる実装を呼び分ける」ポリモーフィズムを支えます。

呼び出し側はshape.area()だけを知っていればよく、中身の実装(円か四角か)は意識しなくて済みます。
また、Python 3.8以降では「プロトコル」という“振る舞いベースのインターフェース”も利用できます。

継承ではなく「このメソッドを持っているならOK」という考え方で、用途に応じて抽象クラスと使い分けます(プロトコルは別セクションで解説します)。

まとめ

今回学習した内容をまとめます。

  • 抽象クラス(ABC)は「設計図」。abc.ABCを継承し、@abstractmethodで“必ず実装すべきメソッド”を示します。
  • サブクラスは抽象メソッドを実装しないとインスタンス化できません。これが実装漏れを防ぎ、コードの品質を高めます。
  • 共通のインターフェースを揃えることで、ポリモーフィズムが働き、差し替え・テスト・保守がぐっと楽になります。

「自分の設計で“約束”を強くしたい」と感じたら、まずは小さな抽象クラスを一つ作ってみましょう。最初は面倒に見えても、プロジェクトが大きくなるほど、その効果を実感できるはずです。

出力結果: