Pythonにおけるメタクラスの利用法

Pythonは動的型付け言語であり、関数やクラスの定義はコンパイル時に決定されるのではなく、実行中に動的に生成される点が静的型付け言語と大きく異なります。

たとえば、Helloというクラスを定義する際には、通常 hello.py モジュールを作成します:

# hello.py
class Hello(object):
    def hello(self, name='world'):
        print(f'Hello, {name}.')

このモジュールをインポートし、以下のように動作を確認できます:

>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> type(Hello)
<class 'type'>
>>> type(h)
<class 'hello.Hello'>

type() 関数はオブジェクトの型を確認するだけでなく、新しい型(=クラス)を動的に生成することも可能です:

>>> def greet(self, name='world'):
...     print(f'Hello, {name}.')
...
>>> Hello = type('Hello', (object,), {'greet': greet})
>>> h = Hello()
>>> h.greet()
Hello, world.
>>> type(Hello)
<class 'type'>
>>> type(h)
<class '__main__.Hello'>

このように、type() を用いた動的クラス生成では、以下の3つの引数を順に指定します:

クラス名(文字列) 継承する親クラスのタプル(多重継承も可能) 属性(メソッドや変数)の辞書

実際、Pythonインタープリタは class 文を処理する際に、内部的に type() を呼出してクラスオブジェクトを生成しています。

通常は class 文でクラスを定義しますが、type() を使えば実行時に柔軟にクラスを構築できます。これは静的言語では非常に複雑な手順(ソースコード生成+コンパイルやバイトコード生成)を要する操作であり、動的言語ならではの特徴です。

メタクラス

もう一つ、クラス定義自体を操作する手段として「メタクラス(metaclass)」があります。

メタクラスは「クラスを生成するためのクラス」であり、次のような階層構造になります:

クラス定義 → インスタンス生成  ↑ メタクラス定義 → クラス生成

つまり、メタクラスを用いると、クラスの生成過程そのものをフックし、クラス定義の修正や拡張を行うことができます。

以下は、メタクラスを使って標準の listadd() メソッドを追加する例です:

# メタクラスは type を継承する必要がある
class ListMeta(type):
    def __new__(mcls, name, bases, namespace):
        # 新しいメソッドを動的に追加
        namespace['add'] = lambda self, value: self.append(value)
        return super().__new__(mcls, name, bases, namespace)

# カスタムリストクラスを定義
class MyList(list, metaclass=ListMeta):
    pass

# 動作確認
>>> L = MyList()
>>> L.add(10)
>>> L
[10]

通常の list には add() が存在しないため、以下のようにエラーになります:

>>> M = list()
>>> M.add(10)
AttributeError: 'list' object has no attribute 'add'

このように、メタクラスはクラス生成時に especially に振る舞うカスタムロジックを差し込むための強力な仕組みです。

メタクラスの典型的な使用例の一つにORM(Object Relational Mapping)フレームワークがあります。ロジック側でデータベースのテーブル構造に応じたクラスを定義するのではなく、メタクラスを通じて導入側の意図に応じて動的にモデルクラスを構築することができます。

タグ: Python metaclass type ORM dynamic-class

7月20日 23:04 投稿