Smile Engineering Blog

ジェイエスピーからTipsや技術特集、プロジェクト物語を発信します

Pythonで抽象クラスを実装する

今回は、Pythonで抽象クラスを実装する方法を解説します。

抽象クラスとは?

プログラミング言語によりある程度違いはありますが、抽象クラスとは、以下の特徴を持ったクラスになります。

  • 継承されることを前提としています。
  • 空実装のメソッドが定義されています。(これを抽象メソッドと呼びます。)
  • このクラスを継承したクラスは、抽象メソッドを再定義(オーバーライド)しなければなりません。

よって、抽象クラスは子クラスに対してメソッドの定義を強制する機能を持っています。

用途としては、多態性ポリモーフィズム)を実装するときに、メソッドの実装漏れ、引数または戻り値の違いによるエラーの回避、などがあります。

実装方法

ここでは、抽象クラスと抽象メソッドの実装方法を解説します。

まず、抽象クラスの実装方法はABCMetaというメタクラスを用います。抽象クラスにしたいクラスのメタクラスABCMetaを設定することにより、そのクラスが抽象クラスになります。(ちなみに、ABCとはAbstract Base Classの略だそうです。)

from abc import ABCMeta

class vehicle(metaclass = ABCMeta):
    pass

また、PythonにはABCというクラスがあり、このクラスを継承することでも抽象クラスを実現することが可能です。

from abc import ABC

class vehicle(ABC):
    pass

このABCの実態は、ABCMetaメタクラスに設定しているだけのクラスです。よって、初めのコードと実質同じです。

次に抽象メソッドを実装するときは、@abstractmethodデコレータを使用します。このデコレータを付与されたメソッドが抽象メソッドになります。抽象メソッドには実際の処理を記述しないので、passまたはraise NotImplementedError()を実装しておきます。(NotImplementedError()とは、未実装エラーを表す例外です。)

from abc import ABCMeta
from abc import abstractmethod

class vehicle(metaclass = ABCMeta):
    """抽象クラスvehicleの定義。"""
    @abstractmethod
    def start(self):
        raise NotImplementedError()

    @abstractmethod
    def stop(self):
        raise NotImplementedError()

ABCMetaには、抽象メソッドが実装されている場合にインスタンスを生成しようとすると、例外が発生する仕様になっています。よって、上記vehicleインスタンスを生成しようとすると、以下のように例外が発生します。

v = vehicle()

# TypeError: Can't instantiate abstract class vehicle with abstract methods start, stop

なお、ABCMetaメタクラスとしているが抽象メソッドが実装されていないクラスや、抽象メソッドは実装されているがABCMetaメタクラスとしていないクラスのインスタンスは生成出来てしまいます。抽象クラスを実装するときは、ABCMetaメタクラスにすることと抽象メソッドの実装をセットで行ってください。

以下のように、抽象クラスを継承して抽象メソッドをオーバーライドすれば、インスタンスの生成が出来るようになります。

from abc import ABCMeta
from abc import abstractmethod

class vehicle(metaclass = ABCMeta):
    """抽象クラスvehicleの定義。"""
    @abstractmethod
    def start(self):
        raise NotImplementedError()

    @abstractmethod
    def stop(self):
        raise NotImplementedError()

class car(vehicle):
    """vehicleを継承したクラスcarの定義。"""
    def start(self):
        print("car start.")

    def stop(self):
        print("car stop.")

class motorcycle(vehicle):
    """vehicleを継承したクラスmotorcycleの定義。"""
    def start(self):
        print("moto start.")

    def stop(self):
        print("moto stop.")

my_car = car()
my_car.start()
my_car.stop()

my_motorcycle = motorcycle()
my_motorcycle.start()
my_motorcycle.stop()

# car start.
# car stop.
# moto start.
# moto stop.

抽象メソッドを表すデコレータについては、@abstractmethod以外にも存在します。ここでは詳細を省きますが、抽象メソッドを表すデコレータを以下に列挙しておきます。

  • @abstractmethod
  • @abstractclassmethod
  • @abstractstaticmethod
  • @abstractproperty

ちなみに、誤って抽象クラスのインスタンスを生成してしまっているコードをflake8にかけてみましたが、こちらではなにも表示されませんでした。抽象クラスのインスタンス生成エラーは実行時にしかわからないようです。

参考