型クラスとは
型クラスとは、主に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):
# バブルソートの実装
...
Sorter
はsort
メソッドを持つべきクラスの「インターフェース」を定義しています。これは、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の良さも犠牲にしているような気がしてしまい、後者の方のやり方は使わないだろうなと思ってしまいます。