2009年5月1日金曜日

メタクラスとクラス拡張(通称:殺人ジョーク)

原文:http://python-history.blogspot.com/2009/04/metaclasses-and-extension-classes-aka.html
原文投稿者:Guido van Rossum

Pythonの最初の実装では、クラス自身もファーストクラスオブジェクトであり、変数に入れたり、関数の属性に渡したり、他のオブジェクトと同じように扱うことができた。しかし、クラスオブジェクトを作成するプロセスは、石版に刻まれた手順がごとく、変更することはできなかった。具体的に説明すると、以下のようなクラス定義があったとする。

class ClassName(BaseClass, ...):
     ...メソッド定義... 

クラスの本体は、新しく作られるローカル辞書の中で実行される。クラス名、ベースクラスが格納されたタプル、そしてこのローカル辞書の3つが内部のクラス作成関数に渡される。この関数が最終的にクラスオブジェクトを作成する責任を持っている。これらの工程は隠れて見えなかったが、そもそもユーザが心配する必要のない、実装の詳細にあたる部分であった。

Don Beaudry氏はエキスパートユーザのために、隠れた可能性を指摘した最初のユーザであった。具体的には、もしクラスそのものもシンプルで特別なオブジェクトであるならば、通常とは違う動作をする、新しい種類のクラスを作ることはできないのか?というものであった。彼はインタプリタにわずかな修正を加え、C言語のコードの拡張モジュールを使って、新しい種類クラスを作れるようにして、それを提案した。この変更が初めて紹介されたのは1995年であった。これは長い間、"Don Beaudryフック"もしくは"Don Beaudryハック"と呼ばれた。名前があいまいだったのはジョークだと思われていたからである。その後、Jim Fulton氏がその修正を一般化し、ドキュメントは不十分ではあったが、言語の一部にした。これは、Python 2.2で新スタイルクラスが導入されて、メタクラスの本当のサポートが始まるまでは、拡張可能なメカニズムとして言語に残っていた。これについては後で触れる。

Don Beaudryフックの基本的な考え方は、クラス作成の最終段階の中でカスタムクラスオブジェクトを作成する、というものであった。例えば、作成されたクラスに対して、ユーザの提供した関数を追加する、といったことが考えられた。具体的には、クラス名、ベースクラス、ローカル辞書の3つの情報を、通常とは異なるクラス作成関数に渡すことができれば、その関数はクラスオブジェクトを作成するための情報を使用してできることは何でもできるのである。唯一私が懸念していたのは、既に確立しているクラスの文法には変更を加えたくない、というものだけであった。

これを行うためには、当時はC言語で新しい型オブジェクトを作成し、呼び出し可能なオブジェクトとしてフックを作成する必要があった。そして、その呼び出し可能な型のインスタンスはクラス文のベースクラスとして使用され、そのクラスが標準のクラスオブジェクトを作成する代わりに、魔法のようにその型オブジェクトを呼び出されるようにするのである。呼び出し可能な型オブジェクトを提供する拡張モジュールを使用することで、クラス作成の振る舞いはこのように変更することができた。

最近のPythonユーザからすると、このようなハックは奇妙に思えるだろう。しかし、当時は型オブジェクトは呼び出し可能ではなかったのである。例えば、int型は組み込みの型ではなく、intオブジェクトを返す組み込み関数であった。int型自身は簡単にはアクセスできなかったし、呼び出すこともできなかった。ユーザ定義クラスもちろん呼び出し可能であったが、元々CALL命令を使ってオブジェクトを作成するというのは特殊なケースであり、組み込みの型はどれも、これとはまったく違う実装になっていた。Don Beaudry氏は最終的に、私の頭に最初のメタクラス、後の新スタイルクラスに繋がるアイディアを植え付け、最終的には古いクラスに引導を渡す役割を果たすことになった。

元々は、Don Beaudry氏自身が作成した、MESSという名前のPython拡張だけが、この機能を利用していた。しかし、1996年の末には、Jim Fulton氏は広く一般的になったExtension ClassesというサードパーティのパッケージをDon Beaundryフックを利用して開発した。Extension ClassesパッケージはPython 2.2がメタクラスをオブジェクト機構の標準の一部として導入して以降は、メリットがなくなった。

Python 1.5では、Don Beaudryフックを利用するために、C言語の拡張を作成しなければならないという制限が取り除かれた。これに加えて、呼び出し可能なベースクラス型のチェック機構を組み込み、"__calss__"という名前の属性をチェックも追加し、もしこの属性が存在していれば、クラス作成コードがこれを呼び出すようになった。私はこの機能についてのエッセーを書いた。これは多くのPythonユーザーにメタクラスのアイディアを紹介する、最初の文章となった。その文章には、頭を爆発させるようなアイディアが多く詰まっていたため、「殺人ジョーク」というニックネーム(モンティ・パイソン参照)がすぐについた。

Don Beaundryフックのもっとも偉大な功績は、クラス作成関数のAPIである。これはPython 2.2に持ち越され、メタクラス機構として実装された。前に説明したように、クラス作成関数は3つの引数を伴って呼び出される。クラス名の入った文字列。そしてベースクラスの入ったタプル(これは空でも、一つだけでも良い)、名前空間の中身が含まれている辞書である。この辞書にはメソッド定義や、その他のクラスレベルのコードが書かれたインデントブロックの内容が含まれる。クラス作成関数の返り値は、クラス名と同じ名前の変数に格納される。

当初は、これはただクラスを作成するためだけの内部APIでしかなかった。Don Beaudryフックは同じ呼び出し規約を使用するようになり、公開APIとなった。このAPIの見落とせないポイントは、メソッド定義が含まれるブロックはクラス作成関数が呼び出される前に定義されるということである。メタクラスはこの部分に対しては影響を与えることはできないため、メソッド定義が実行される名前空間の初期コンテンツに作用することはできないのである。

Python3000ではこの部分も変更され、メタクラスは通常とは異なるマッピングオブジェクトを提供し、その中でクラス本体の実行を行わせることができるようになる。これをサポートするために、明示的にメタクラス定義する文法も変更される。この目的のために、ベースクラスのリストの中でキーワード引数の文法を使用できるようになる。

訳注:メタクラスの文法は以下のように変更された

# Python 2.x
class C(object):
  __metaclass__ = M
  ...
# Python 3.x
class C(metaclass = M):
  ...

次のエピソードでは、メタクラスの考えが、いかにして2.2の新スタイルクラスの導入につながったのか、また、3.0の中で最終的に古い形式のクラスがどのように終局を迎えたか、ということについて、詳しく書きたいと思う。

0 件のコメント:

コメントを投稿