米国株売買シミュレーションで学ぶPythonプログラミング - 第3回 抽象クラス・メソッド

米国株売買シミュレーションのアプリでは、abcパッケージを利用して抽象クラス・メソッドを実現しています。この記事では、抽象クラス・メソッドを実装する方法と、(動的型付け言語のPythonで)抽象クラス・メソッドを実装した理由を紹介します。

※当連載で扱うアプリのソースはGitHub上に公開してあります。 https://github.com/yusukemurayama/ppytrading

抽象クラス・メソッドの使い方

ドキュメントの通りですが、簡単にまとめておきます。

抽象クラス・メソッドを定義する

クラスを抽象化する時は、クラスを定義するときにmetaclass=abc.ABCMetaを与えます。そして、抽象化したメソッドにabc.abstracemethodデコレータを適用します。

import abc


class AbstractBase(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def method1(self):
        pass

    @abc.abstractmethod
    def method2(self):
        pass

    def pmethod(self):
        return '{}.pmethod'.format(type(self).__name__)

これで抽象クラス・メソッドを定義することができました。上記例では、抽象メソッドmethod1とmethod2、通常のメソッドpmethodを定義しています。

※これはPython3系の書き方です。Python2系(2.7.11)などでは動作しない点にはご注意ください。

抽象化したクラスを継承する

抽象クラスを定義したら、それを継承してクラスを作成していくことができます。

class AbstractChild1(AbstractBase):
    def method1(self):
        """method1をオーバーライド"""
        return '{}.method1'.format(type(self).__name__)

    def method2(self):
        """method2をオーバーライド"""
        return '{}.method2'.format(type(self).__name__)

継承したクラスを定義したら、インスタンスを生成してオーバーライドしたメソッドを呼ぶことができます。

instance = AbstractChild1()
print(instance.method1())  # 「AbstractChild1.method1」と表示されます。
print(instance.method2())  # 「AbstractChild1.method2」と表示されます。

また、抽象クラスで定義されたメソッドも呼ぶことができます。

instance = AbstractChild1()
print(instance.pmethod())  # 「AbstractChild1.pmethod」と表示されます。

抽象化して制限されること

抽象メソッドを定義した抽象クラスは、インスタンスを生成したときにTypeErrorが発生します。

AbstractBase()  # TypeErrorが発生します。

また、継承先のクラスでabstractmethodが実装されていない場合もTypeErrorが発生します。

class AbstractChild2(AbstractBase):
    pass

AbstractChild2()  # TypeErrorが発生します。

クラスを抽象化した理由

Javaの様な静的型付け言語と違い、今回のアプリ作成においては抽象化することは必須ではありません。上に挙げた例を見ても、「なんでわざわざabstractmethodをつけたんだろう」と思った方もいるかもしれません。

その理由は継承したクラスでメソッドの定義漏れを防ぐためです。このことを、抽象化していないクラスと抽象化しているクラスを比較して説明していきます。

抽象化していないクラス

method1・method2が定義されたNoAbstractBaseがあるとします。

class NoAbstractBase(object):
    def method1(self):
        pass

    def method2(self):
        pass

このクラスを継承して、NoAbstractChildを定義します。ただし、TYPOによって、method2をmethods2としてしまったとします。

class NoAbstractChild(NoAbstractBase):
    def method1(self):
        """method1をオーバーライド"""
        return '{}.method1'.format(type(self).__name__)

    def methods2(self):
        """method2をオーバーライド(メソッド名のスペルミス)"""
        return '{}.method2'.format(type(self).__name__)

このときNoAbstractChildのインスタンスは生成し、method2を呼ぶとNoneが返却されます。Noneが返ってくる理由は、サブクラスでオーバーライドされていないので、親のmethod2(結果はNone)を呼ぶためです。

instance = NoAbstractChild()
print(instance.method1())  # 「NoAbstractChild.method1」と表示されます。
print(instance.method2())  # 「None」と表示されます。

今回作成した売買シミュレーションアプリでは、オーバーライドしたメソッドがNoneを返すケースがあります。例えば、「買いシグナルが出ていなければNoneを返す」といった具合です。そのため、抽象メソッドとして定義していないと「メソッドが定義されていないのか、買いシグナルが発生していないのかが判断できない」ということが起こりえます。

抽象化しているクラス

オーバーライドして使うメソッドを抽象化している場合は、インスタンス生成のタイミングでTypeErrorが発生します。その例外発生により、メソッドの実装漏れを知ることができます。

import abc


class AbstractBase(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def method1(self):
        pass

    @abc.abstractmethod
    def method2(self):
        pass

    def pmethod(self):
        return '{}.pmethod'.format(type(self).__name__)


class AbstractChild3(AbstractBase):
    def method1(self):
        """method1をオーバーライド"""
        return '{}.method1'.format(type(self).__name__)

    def methods2(self):
        """method2をオーバーライド(メソッド名のスペルミス)"""
        return '{}.method2'.format(type(self).__name__)

上記AbstractChild3はmethod2をオーバーライドしていないので、TypeErrorが発生します。

instance = AbstractChild3()  # TypeErrorが発生します。

抽象クラス・メソッドのサンプルとテストケース

今回使ったサンプルと、そのテストケースを掲載しておきます。

この記事が役に立った場合、シェアしていただけると励みになります!!

この記事に関する質問は@ysk_murayamaでご連絡ください。可能な内容であれば回答します!