このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。

【Python入門】ダックタイピングと抽象クラス

ダックタイピングと抽象クラス

 ここでは、Pythonでのオブジェクトの振る舞いをより深く理解するために、「ダックタイピング」と「抽象クラス」について解説します。ダックタイピングではオブジェクトが備える「振る舞い」さえ合致していれば、クラスを問わず同様に扱えるのが大きな特徴です。一方、抽象クラスは「このメソッドを実装しないといけない」ことを派生クラスに強制できるしくみで、オブジェクト指向設計の土台として便利です。ここでは、サンプルプログラムを例示しながら、両者の使い道を示します。

プログラムのダウンロード

 「ダウンロード」から実行できるサンプルプログラムがダウンロードできます。ファイルは、ESET Endpoint Securityでウイルスチェックをしておりますが、ダウンロードとプログラムの実行は自己責任でお願いいたします。

1.ダックタイピングの考え方

1.1.「アヒルのように鳴けばアヒル」

 ダックタイピング(duck typing)とは「オブジェクトが持つメソッドや属性」を基準に扱いを決める考え方です。もしあるオブジェクトが「あるメソッド」を備えていれば、どのクラス出身かは気にせず同じように扱えます。
 たとえば「鳴く(quack)メソッド」を持つならば「アヒル(duck)」として振る舞える、という考え方です。Pythonではこの考え方が広く適用されており、クラス名ではなく「動作可能か」で判断する仕組みを多用します。

1.2.ダックタイピングの例

 次のサンプルでは、同じ「speak」メソッドを備えたオブジェクトならば、クラスが異なっても問題なく扱えます。たとえば以下のように「Dog」と「Robot」がともにspeak()を提供していれば、同じ関数make_speak()で呼び出せます。

def make_speak(entity):
    entity.speak()  # entityがspeakメソッドを備えていればOK

class Dog:
    def speak(self):
        print("わんわん")

class Robot:
    def speak(self):
        print("ピッピッ")

dog = Dog()
robot = Robot()

make_speak(dog)    # わんわん
make_speak(robot)  # ピッピッ

実行結果

わんわん
ピッピッ

 このように、クラス名や継承関係に関わらず、「対応するメソッドが存在すれば呼び出せる」 のがダックタイピングの利点です。

2.抽象クラス(Abstract Class)を使う

2.1.抽象クラスとは

 抽象クラスとは、「インスタンス化(オブジェクトの生成)を許可しないクラス」のことです。Pythonでは、標準ライブラリのabcモジュールを使い、クラスを継承するときに「必ずこのメソッドを実装してほしい」という要求を派生クラスに強制できます。これを抽象基底クラス(ABC: Abstract Base Class)と呼びます。

2.2.具体例:図形クラス

 以下の例では、「Shape」という抽象基底クラスを定義し、派生クラスは必ずarea()メソッドを実装するように強制しています。もし実装しなければオブジェクトを作れない仕組みです。

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        """図形の面積を返す抽象メソッド"""
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return math.pi * (self.radius ** 2)

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

def main():
    # Shapeを継承するクラスはareaを必ず実装しなければならない
    shapes = [Circle(3), Rectangle(2, 5)]
    for shape in shapes:
        print(f"面積: {shape.area():.2f}")

if __name__ == "__main__":
    main()
  • Shape(ABC)abcモジュールのABCを継承することで抽象クラス化。
  • @abstractmethod : このデコレーターを付けたメソッドは必ず派生クラスで実装が必須。
  • CircleRectanglearea()をオーバーライドしなければ、エラーが起こりインスタンス化できない。

上記プログラムを実行すると、以下のように出力されます。

実行結果

面積: 28.27
面積: 10.00

 これにより「円」「長方形」といった形状は、いずれも「Shape」として扱えますが、必ずarea()メソッドを備えていることが保証されます。これは抽象クラスの大きな利点です。

まとめ

  • ダックタイピング : メソッドや属性を持っていれば、それがどのクラスかは問わず同じ処理を適用できる。Pythonらしい柔軟な型の考え方。
  • 抽象クラス : 「このメソッドは必ず実装せよ」という契約を強制するための基底クラス。abcモジュールと@abstractmethodを使って定義する。
  • 両者を使い分ければ、設計段階で適度な柔軟性と安全性を確保できる。小規模な処理ならダックタイピングで気軽に、多くの開発者が関わる大規模プロジェクトでは抽象クラスを設けて実装忘れを防ぐことが多いです。

 次のステップとして、特殊メソッドやデコレーターなどを使ってクラスをさらに拡張し、Python独自のオブジェクト指向機能を活かした設計を学びましょう。