スライドショー

Pythonで分かる!ポリモーフィズムとは?使い方について解説

Pythonでポリモーフィズムが使いたい
abcで抽象クラスが使えるらしいけど…

他の言語をやっていた人だと使いたくなるポリモーフィズム。Pythonでもやってみましょう。

こんにちは、ライターのフクロウです。Pythonのインストラクターをやっています。

この記事では一つのインターフェイスに対して複数の実装をするポリモーフィズムについて学んでいきます。綺麗なコードを書きたい、大きなプログラムを効率的に開発したいという方には是非知っておいて欲しい概念です。

この記事はこんな人のために書きました。

  • ポリモーフィズムという言葉の意味が知りたい
  • Pythonで抽象基底クラスが使いたい
  • Pythonでポリモーフィズムを試してみたい

ポリモーフィズムとは

ポリモーフィズムとは何でしょうか。Wikipediaによると以下のように解説されています。

ポリモーフィズム(英: Polymorphism)とは、プログラミング言語の型システムの性質を表すもので、プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)についてそれらが複数の型に属することを許すという性質を指す。

ポリモーフィズム -- wikipediaより引用

ポリモーフィズムはいくつかの種類に分けられて、例えば以下のものが知られています。

  • アドホックポリモーフィズム
  • パラメータポリモーフィズム
  • サブタイプポリモーフィズム

ポリモーフィズム自体についての解説はニコニコ大百科の記事が分かりやすいと思います。

さて、Pythonはオブジェクト指向言語で、abcというモジュールを使うことでポリモーフィズムの恩恵を受けるのがこの記事の目的です。

そしてここでのポリモーフィズムとは、同じインターフェイスで異なる型のオブジェクト向けに動作を提供するものです。例えば同じ+演算子でもポリモーフィズムに則って実装されているため、int型の足し算もsrt型の足し算も出来てしまいます。

>>> 1+2
3

>>> "あ"+"い"
'あい'

add_int、add_strのように別の関数を用意しないでも同様の事ができています。たぶんこっちのほうが分かりやすいですよね。

クラスでも、異なるクラスで同じ名前のメソッドを用意することで実現できます。

これができると、様々なクラスを定義したとしてもどのクラスのインスタンスにも同じ名前のメソッドがあることになります。結果プログラムの見通しが良くなって大規模な開発がしやすくなるはずです。この記事ではこれを扱います。

抽象基底クラス

abcモジュールで抽象基底クラスを作る

Pythonにはabcという標準ライブラリが用意されています。これを使うことでポリモーフィズムを強制することができます。

使い方は簡単で、ひな形としてインターフェイスの模範にしたいクラスを以下のように用意します。

from abc import ABCMeta, abstractmethod, ABC

class AbstractAgent(metaclass=ABCMeta):
    
    @abstractmethod
    def __init__(self, hp=100, mp=100):
        self.hp = hp
        self.mp = mp
    
    @abstractmethod
    def __str__(self):
        raise NotImplemented()
    
    @abstractmethod
    def attack(self, target):
        target.hp -= 10
        print(f"{target}へ体当たり攻撃!")

雛形のクラス(抽象基底クラスと呼びます)では、親クラスを書いていた括弧の中に「metaclass=ABCMeta」と記述します。これで抽象基底クラスとして定義されました。

また、どのクラスでも__init__と__str__、attackの3つのメソッドを用意させたいとしましょう。その場合それぞれのメソッドに「@abstractmethod」というデコレータを用意します。

このデコレータがついたメソッドは、この抽象基底クラスを継承したサブクラスでも必ず用意しなければ行けないメソッドになります。

これを抽象メソッドといいますが、abcモジュールではこのメソッドの中にちゃんと処理も書くことができます。

ABCMetaを使ったクラスの注意点

抽象基底クラスからインスタンスを作ることは出来ません。例えば以下のようにしてインスタンスを作ろうとすると、エラーが発生します。

>>> p1 = AbstractAgent()

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-71-7f498ecb08b1> in <module>
----> 1 p1 = AbstractAgent()

TypeError: Can't instantiate abstract class AbstractAgent with abstract methods __init__, __str__, attack

ABCMetaがつかない普通の親クラスであればこの操作は可能ですが、このように定義したクラスは実体を持てないことに注意しましょう。

抽象基底クラスを継承したサブクラス

サブクラスを作ってみる

それでは先程のクラスを継承したサブクラスを作ってみましょう。

ここではAbstractAgentという抽象基底クラスを継承した、SpellCaster、SwordManというクラスを作ります。(ゲームを開発してると思ってください)

class SpellCaster(AbstractAgent):
    def __init__(self, name, hp=100, mp=200):
        super().__init__(hp=hp,mp=mp)
        self.name = "魔法使い「"+str(name)+"」"
        
    def __str__(self):
        return f"■name: {self.name}\n■hp:{self.hp}\n■{self.mp}"
    
    def attack(self, target):
        target.hp -= 20
        self.mp -= 10
        print(f"{self.name}が{target.name}へ魔法攻撃!")
        
class SwordMan(AbstractAgent):
    def __init__(self, name, hp=200, mp=100):
        super().__init__(hp=hp, mp=mp)
        self.name = "剣士「"+str(name)+"」"
        
    def __str__(self):
        super().__str__()
    
    def attack(self, target):
        target.hp -=15
        self.hp -= 5
        print(f"{self.name}が{target.name}へ剣で攻撃!")
        
p1 = SpellCaster("ジャック")
p2 = SwordMan("ジョン")

p1.attack(p2)
[出力結果の例]
魔法使い「ジャック」が剣士「ジョン」へ魔法攻撃!

この2つのクラスは抽象基底クラスと同様に3つのメソッドを持っていますね。

それぞれがそのクラス向けの機能を実装していますが、インターフェイスは同じです。同様の名前でアクセスでき、仮に定義を忘れても次のセクションで紹介するようにエラーで知らせてくれます。

また、これはabcを使わない場合でも有効ですが、親クラスのメソッドに「raise NotImplemented()」を書いておくことで、親クラスのメソッドをそのまま使った場合にエラーを出すことが出来ます。

>>> print(p2)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-80-ad0749cbb249> in <module>
----> 1 print(p2)

<ipython-input-78-43416f20f217> in __str__(self)
     18 
     19     def __str__(self):
---> 20         super().__str__()
     21 
     22     def attack(self, target):

<ipython-input-70-438fdc87a1fa> in __str__(self)
      8     @abstractmethod
      9     def __str__(self):
---> 10         raise NotImplemented()
     11 
     12     @abstractmethod

TypeError: 'NotImplementedType' object is not callable

大規模なプログラムを書くときに有効な方法です。ぜひ使ってみてください。

サブクラスの注意点

抽象基底クラスを継承したクラスの定義は普通と同じでした。ですが、必要なメソッドが足りなければエラーがでるので注意して下さい。

class SpellCaster(AbstractAgent):
    pass
        
p1 = SpellCaster()
[エラー例]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-71-7f498ecb08b1> in <module>
----> 1 p1 = AbstractAgent()

TypeError: Can't instantiate abstract class AbstractAgent with abstract methods __init__, __str__, attack

このエラーはクラスを定義しただけでは発生しません。

クラスからインスタンスを作る段階でエラーが発生して足りないインターフェイスを教えてくれます。

また、たとえ途中までメソッドを定義していたとしても、一つでも足りなければ先ほどと同様にエラーが発生します。

class Archer(AbstractAgent):
    def __init__(self, name, hp=150, mp=150):
        super().__init__(hp=hp, mp=mp)
        self.name = "弓兵「"+str(name)+"」"
    
    def attack(self, target):
        target.hp -=15
        self.hp -= 5
        print(f"{self.name}が{target.name}で弓矢で攻撃!")
        
p1 = Archer("花子")
[エラー例]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-76-19d1914142c6> in <module>
      9         print(f"{self.name}が{target.name}で弓矢で攻撃!")
     10 
---> 11 p3 = Archer("花子")

TypeError: Can't instantiate abstract class Archer with abstract methods __str__

この手法のメリット

上のようにabcモジュールを使って抽象基底クラスを作ることで、複数のクラスで同等のインターフェイスを用意することができます(出来ていなければエラーが出るため)。

普通の親クラスであれば、実装を忘れたメソッドがあっても親クラスのメソッドが呼ばれるだけです。この方法だとインタプリタがしっかり教えてくれるので、バグが減るはずです。

まとめ

いかがだったでしょうか。abcモジュールを使ったクラスの作成は今までやったことがないという方も多かったと思います。

今回は抽象基底クラスを使ったポリモーフィズムを紹介しましたが、プログラミングを勉強して行けばこれ以外にも様々な場面でこの言葉に遭遇するでしょう。

文章で読むと難しいことでも、実装例を見てみると理解できることは多いはずです。是非手を動かしてプログラミングの勉強してください。

LINEで送る
Pocket

無料でSEからWebエンジニアへ転職しませんか?



侍エンジニア塾では、完全未経験の方から現在SEだけどプログラミングはやっていないという経験者まで、幅広い方々の人生を好転させるプログラミング指導を行ってきました。SEの方とお話していくなかで、

  • システムエンジニアという職業だけどコードが書けない
  • 事務作業が多くスキルがないため将来が不安
  • スクールに通うと完全未経験者と同じスタートになるからレベルが合わない
という、すでに知識があるSEならではのお悩みがあることに気づきました。そんな方におすすめなのが、弊社の「転職コース 」です。

弊社では、マンツーマンでレッスンを行いますので、現在お持ちの知識レベルからカリキュラムを作成いたします。さらにこちらの転職コースは無料で受講を始められて転職成功でそのまま卒業できるというとてもお得なコースとなっています。

既に知識のあるSEといっても転職は年齢が若いほど受かりやすいため、まずは無料体験レッスンで今の現状や理想の働き方について一緒に考えていきましょう。

まずは無料体験レッスンを予約する

書いた人

フクロウ

フクロウ

第一言語はPythonです。
皆さんRustやりましょう。

おすすめコンテンツ

あなたにぴったりなプログラミング学習プランを無料で診断!

プログラミング学習の効率を劇的に上げる学習メソッドを解説