2025/08/20

Pythonで型クラスは実装できるか

型クラスとは

型クラスとは、主にHaskellなどの関数型言語で導入されている機能で、特定の振る舞いを持つ型をまとめるための仕組みです。

例えば、整数(int)や浮動小数点数(float)は、足し算ができます。これは、intやfloatが「足し算できる」という共通の振る舞いを持っているからです。型クラスでは、この「足し算できる」という振る舞いを「Addable」といった型クラスを定義し、その型クラスを満たす型だけが足し算を許される、といったルールを設けることができます。これにより、コードの再利用性を高めつつ、安全性を確保できます。

Pythonには型クラスは存在しない

結論から言うと、PythonにはHaskellのような厳密な「型クラス」は存在しません。

Pythonは動的型付け言語であり、変数の型は実行時に決まります。そのため、特定の型が特定の振る舞いを満たすかどうかは、実行してみないと分からないという性質があります。


def add(a, b):
    return a + b

add(1, 2)  # int同士の足し算
add('Hello', 'world')  #文字列の結合

上記の例では、add関数はint型でもstr型でも動作します。これは、両方の型が+演算子に対応しているためです。Pythonでは、このような振る舞いの共通性を、ダックタイピングという考え方で扱います。

もしそれがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルである

つまり、特定の振る舞い(メソッドや演算子)を持っていれば、それはその振る舞いを期待する場所で使える、という考え方です。型を厳密にチェックするのではなく、実行時の振る舞いに注目するのはPythonの大きな特徴です。

Pythonでの「型クラス」的な実装方法

では、Pythonで型クラスに近いことを実現したい場合、2つの方法があります。

抽象基底クラス

Pythonには、abcモジュールを使った抽象基底クラスの仕組みがあります。これは、クラスが満たすべきメソッドを定義し、そのクラスを継承するサブクラスがそのメソッドを実装するということを強制します。


from abc import ABC, abstractmethod


class Sorter(ABC):
    @abstractmethod
    def sort(self, data):
        pass


class BubbleSorter(Sorter):
    def sort(self, data):
        # バブルソートの実装
        ...

Sortersortメソッドを持つべきクラスの「インターフェース」を定義しています。これは、Haskellの型クラスが特定の関数を持つべき型のルールを定めるのと似ています。

プロトコル

Python3.8以降で導入されたtyping.Protocolは、さらに型クラスに近い概念を提供します。ABCが継承によって振る舞いを強制するのに対し、プロトコルは構造的な型付けに基づいています。つまり、特定のメソッドや属性を持っていれば、明示的に継承しなていなくてもプロトコルを満たしていると見なされるます。これは、ダックタイピングに静的型付けの側面を加えたものと言えます。


from typing import Protocol, runtime_checkable


@runtime_checkable
class Addable(Protocol):
    def __add__(self, other) -> 'Addable':
        ...

def add_all(items: list[Addable]):
    result = items[0]
    for item in items[1:]
        result = result + item
    return result


class MyNumber:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return MyNumber(self.value + other.value)

    def __repr__(self):
        return f"MyNumber({self.value})"

my_numbers = [MyNumber(1), MyNumber(2), MyNumber(3)]

pritnt(add_all(my_numbers)) # -> MyNumber(6)

Addableは、__add__メソッドを持つ型であることを示しています。MyNumberは明示的にAddableを継承していませんが、__add__を持っているため、add_all関数に渡すことができます。

まとめ

一応、型クラス似たようなことをできなくはないようですが、個人的にはそもそもダックタイピングがあまり好きではなく、型ヒントによってダックタイピングの可読性の低下や実行時エラーのリスクが軽減されるとはいえ、静的型チェックを100%信頼しきれず、Pythonの良さも犠牲にしているような気がしてしまい、後者の方のやり方は使わないだろうなと思ってしまいます。