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の中で最終的に古い形式のクラスがどのように終局を迎えたか、ということについて、詳しく書きたいと思う。

ヘビ来襲!

原文:http://python-history.blogspot.com/2009/04/and-snake-attacks.html
原文投稿者:Greg Stein

1995年に私は初めてPythonと遭遇した。Pythonを"ヘビ"として言及することは禁止されている。Pythonは爬虫類の方ではなく、モンティ・パイソンにちなんで命名されたからである。 もし誰かがあなたを攻撃していたら、犯人はKnights who say NiかおそらくRabbit of Caerbannogである。

訳注:Knights who say Niは、モンティ・パイソンの2作目のコメディ映画である、モンティ・パイソン・アンド・ホーリー・グレイルに登場する、騎士のグループのこと。Rabbit of Caerbannogはその映画に登場する殺人ウサギ。

とにかく、1994には私はLPMUD(マルチユーザのRPG)の中でフィクション上の悪役と闘っていた。Webはかろうじて存在していたが、ブロードバンドという言葉は聞いたことがなく、バンド幅が少なくてすむゲームが一般的だった。

もう少し戻ろう。1979年。私が最初に触れたコンピュータはAppleⅡだった。当時のお気に入りのゲームの一つがColossal Caveであった。その後は、Zorkをプレイすることを覚えた。私はインタラクティブなフィクションというコンセプトと、これらのストーリーをコンピュータが制御しているということに心を奪われた。これらのゲームに捕まえられて、コンピュータのある生活がそこから始まった。(そう、25年以上して、Don Woods氏に会うことができたときの私の喜びは想像できるでしょうか?)

訳注:Don WoodsはColossal Caveの開発者

MUDをプレイするのはとても面白かったが、私はこれらのゲームの作成を手伝いたいと思うようになってきた。私はLPMUDのゲーム作家でコーダーで設計者でもあるJohn Viega氏に会った。そのとき彼はヴァージニア大学のコンピュータグラフィックスラボでAliceと呼ばれるシステムに関する仕事をしていた。このシステムはコンピュータに詳しくない人向けのシステムで、アニメーションを作成したい人向けに学びやすい言語を必要としていた。彼らは明快さ、パワー、シンプルさからPythonを選択した。

John氏はPythonの熱烈なファンで、私にPythonを紹介して「これだけは覚えて下さい!」「いいよ、いいよ」という会話した。その言語は簡単だがパワフルで、苦労しなくても何でもできた。

それが1995年の2月。私はまだそこから戻ってきていない。

そのときはあまり思っていなかったが、その後の私の仕事と人生にとって、Pythonは重要な軸となった。Guido氏の作品に感謝したいと思います。

Pythonの"関数型"の機能の起源

原文:http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html
原文投稿者:Guido van Rossum

私はPythonが関数型言語から強く影響を受けたことがあるとは一度も考えたことはなかったが、そのように言ったり思ったりする人はいるようである。私がそれまでに慣れ親しんできた言語は、C言語やAlgol 68のような命令的な言語である。Pythonの関数はファーストクラスオブジェクトとして作成されているが、Pythonを関数型のプログラミング言語として見たことはない。しかし、以前から、リストと関数を使用して、もっと多くのことをしてみたいと思うユーザがいたことは確かである。

リストに対して行う一般的な操作としては、リストのそれぞれの要素に対して関数を適用し、新しい配列を作成するというものがある。サンプルコードを上げると以下のような操作になる。

def square(x):
    return x*x

vals = [1, 2, 3, 4]
newvals = []
for v in vals:
    newvals.append(square(v))

LispやSchemeのような関数型言語では、このような操作は言語の組み込み関数として用意されている。そのため、最初のころは、これらの言語に慣れていたユーザは同等の機能をPythonに実装していた。

def map(f, s):
    result = []
    for x in s:
            result.append(f(x))
    return result

def square(x):
    return x*x

vals = [1, 2, 3, 4]
newvals = map(square,vals)

上記のコードの微妙な部分としては、リストの要素に対して適用したい処理を、完全に独立した関数として別に定義しなければならないということを好まない人も少なからずいる、ということである。Lispのような言語では、map関数を呼ぶ時に簡単に関数を定義することができる。例えば、Schemeでは、無名の関数を作成することができるため、lambdaを使用することで1行で配列に対して処理を行うことができる。

(map (lambda (x) (* x x)) '(1 2 3 4))  

Pythonは関数をファーストクラスオブジェクトとして作成するが、関数型言語と同じように無名関数を作成する機能は持っていなかった。

1993年の終わり頃になると、何人ものユーザが、無名関数を作成するためのさまざまなアイディアを投稿してきた。これと同時に、リストを操作する、map(), filter(), reduce()といった関数も一緒に提案があった。例えば、"Pythonプログラミング"の著者のMark Lutz氏はexecを使用して無名関数を作成するコードをいくつか投稿した。

def genfunc(args, expr):
    exec('def f(' + args + '): return ' + expr)
    return eval('f')

# サンプルの使用方法
vals = [1, 2, 3, 4]
newvals = map(genfunc('x', 'x*x'), vals)

Tim Peters氏は以下のようにユーザが入力できるように、構文を多少簡単にするための解決策を補足として提案した。

vals = [1, 2, 3, 4]
newvals = map(func('x: x*x'), vals)

上記の例から、関数型のような機能が要望されているのは明らかであった。しかし同時に、文字列として書かれた無名関数をexecの中で手動で処理するのは極めてトリッキーなやり方に見えた。そのようなこともあり、1994年の1月には、map(), filter(), reduce()の3つの関数が標準ライブラリに追加されることとなった。これに加えて、より直感的な文法で無名関数を式として作成するための、lambda演算子も導入された。lambda演算子の使い方は以下の通りである。

vals = [1, 2, 3, 4]
newvals = map(lambda x:x*x, vals)

この機能追加は、初期のころに、ユーザの提供したコードによって行われたという意味で、Pythonのコミュニティにとっては意義深いものであった。しかし、残念なことに著者の名前を思い出せず、SVNのログにもこの記録がないのである。もしこの読者の中にこのコードの作者がいるのであれば、コメントを是非とも残して欲しい。

私は"lambda"という用語の使用については全面的に賛成ではなかったが、代替案もなかったために、Pythonに取り込まれることになった。その用語を選択したのは現在は無名の貢献者であるが、当時は良く悪くも、現在と比較して、少ない議論でどんどん大きな変化を行わなければならない時期であった。

lambdaはただ、無名関数を定義するための文法的なツールを実現する、という意図だけで作成されたものである。しかし、この用語の選択によって、予期していなかったような問題がいくつも起きることになった。例えば、関数型言語に通じたユーザは、他の言語のlambdaの意味と完全に一致していることを期待してPythonのlambdaを使用した。しかしPythonの実装には高度な機能が大きく欠けているという違いがあったのである。例えば、lambdaにまつわる微妙な問題として、lambdaの式がスコープ内の周りの変数を参照できなかったというものである。以下のコードはmap関数の中のlambda関数で未定義の変数'a'を参照したというエラーが発生して処理が止まってしまうのである。

def spam(s):
    a = 4
    r = map(lambda x: a*x, s)

直感的ではないが、この問題を回避する方法としては、デフォルト引数と隠されたパラメータとしてを通じてlambda式に値を渡すというものがある。

def spam(s):
    a = 4
    r = map(lambda x, a=a: a*x, s)

この問題に対する「正しい」解決策としては、内部関数に対して、暗黙のうちにスコープ内のすべてのローカル変数の参照を渡すようにするというものがある。これは「クロージャ」として知られており、関数型言語の重要な機能の一つである。しかし、この機能はPython 2.1では"from future"でimprotしたときのみの先行機能機能として、Python 2.2では正式な機能として、ようやく取り入れられた。

lambdaや他の関数型の機能導入の動機になっていたmap, filter, reduce関数は、リスト内包表記と、ジェネレータ表現に取って代わられようとしている。実際、Python 3.0ではreduce関数は組み込み関数のリストから除外されることになっている。(lambda, map, filterはそのまま残るので苦情を言う必要はありません :-)

訳注:reduceは組み込み関数からfunctoolsモジュールへ移動した。

私はPythonを関数型言語として見ていないが、クロージャの導入は価値があったと考えている。クロージャは他の多くのプログラミング機能の拡張の開発の役に立っているからである。例えば、新スタイルクラスや、デコレータなど、いくつかの新しい機能はクロージャを利用している。

最後になるが、Pythonは何年もかけて、いくつもの関数型言語機能を取り込んできたが、まだ「本当の」関数型プログラミング言語に見られる機能がいくつも欠けている。例えば、末尾再帰のような、最適化に関する部分にはまだ取り組んでいない。Pythonは極めて動的な性質があるため、HaskellやMLといった関数型言語で行われるような、コンパイル時の最適化を行うことはできないが、それはそれでかまわないと考えている。

大規模(壮大)な名前の変更

原文:http://python-history.blogspot.com/2009/03/great-or-grand-renaming.html
原文投稿者:Guido van Rossum

Pythonが最初に作られた当初は、私はいつも、時々サードパーティのライブラリとリンクする程度で、基本的にはスタンドアローンのプログラムとしてしか動かすことはないだろうと考えていた。そのため、ソースコードの中ではC言語のリンカのレベルのグローバル名を自由に定義していた。例えば、'object', 'getlistitem', 'INCREF'などといったグローバルな名前が使用されていた。Pythonの人気が高まるにつれて、組み込みバージョンのPythonについての問い合わせが増えてきた。組み込みというのは他のアプリケーションにライブラリとしてリンクして使用することである。EmacsがLispインタプリタを統合しているのとはそれほどの違いはない。

しかし、Pythonのプログラムが持つグローバル名と、組み込みを行うアプリケーションが定義しているグローバル名の名前の衝突が発生するために組み込み作業は複雑であった。特に、'object'という名前は人気があったために衝突が多かった。この問題に対処するために、すべてのPythonのグローバル名の先頭に"Py"もしくは"_Py"(内部的にしか使用しないが、技術的な理由からグローバルにしているもの)、"PY"(マクロ向け)を付けるという命名規約を導入することにした。

すでにサードパーティ製のモジュールがたくさん作られていたため、移行作業は後方互換性に考慮して行われることになった。コアな開発者であるほど以前の名前を覚えているだろうということを考え、2つのフェーズが用意された。フェーズ1ではリンカからは古い名前に見えるが、ソースコードでは新しい名前が使用できるように、大量のC言語のプリプロセッサマクロを使用して新しい名前を古い名前に変換するようにした。フェーズ2では、リンカからは新しい名前が見えるようにしたが、移行作業が遅れていた拡張モジュールのことを考慮し、今度は古い名前から新しい名前に変換するという、また別のマクロのセットを用意した。この2つフェーズの中では、古い名前と新しい名前が混在することができたので、プログラムが動作しなくなるということはなかった。

Subversionのログから、これらの名前の変更の歴史を少し調べてみた。1995年の1月12日のr4583から作業が開始されていることが分かった。この時に大規模な名前の変更が行われることが通知され、新しい名前をもつヘッダファイルが導入された。しかし、1996年の12月の時点ではまだ.cのソースファイルの名前の変更作業は継続していた。"Grand Renaming(壮大な名前の変更)"という内容で調べると、名前を変更しているだとか、名前を変更したという、この時期のチェックインコメントを頻繁に見つけることができる。後方互換性のためのマクロが最終的に削除されたのは2000年の5月で、Python 1.6のリリース作業の中で行われた。r15313のチェックインコメントの中に、このイベントを祝うコメントが書かれている。

この壮大な作業の功績の多くは、Barry Warsaw氏とRoger Masse氏によるものである。彼らはスクリプトの助けも得ながら、ファイルの中の名前の変更という、地味な作業に参加して次々と進めてくれた。彼らは多くの標準ライブラリにユニットテストを追加するという、退屈な作業をコツコツと行うこともしてくれたのである。

Wikipediaでは、かつて行われた大規模な名前変更(Great Renaming)の出来事ついて紹介されている。見たところ、USENETのグループの再編成のことのようである。私はおそらく、無意識のうちに「PythonのGreat Renaming」というイベントの名前を付けたということを思い出した。これ以降に行われた大規模な名前の変更として見つけられるのは、Pythonのドキュメント生成にも使用していたSphinxである。Zopeもかつて大規模な名前の変更を行ったことがある。ここであげた例と比較すると小さな話ではあるが、最近ではPy3kの議論の中で、PyStringをPyBytesに変更しようという議論も行われている。

大規模、もしくは壮大な名前の変更は、開発者のコミュニティにとっては、高い確率でケガを伴うイベントである。プログラマの頭の配線をつなぎ直す必要があるし、ドキュメントも書き直さなければならない。また、名前の変更前のパッチを名前の変更後に統合するという複雑な作業が発生することもある。これは特に、名前の変更が行われていないブランチが残っている場合多く発生する問題である。

モジュールの動的ロード

原文:http://python-history.blogspot.com/2009/03/dynamically-loaded-modules.html
原文投稿者:Guido van Rossum

Pythonの実装アーキテクチャは、開発の当初からC言語で書く拡張モジュールが作成しやすいような仕様になっている。しかし、初期の頃には動的なロードを行う技術がまだ明確になっていなかったため、このような拡張機能は、ビルド時にPythonと静的にリンクをする必要があった。これを行うために、C言語の拡張モジュールにはPythonと自分の拡張モジュールをビルドするMakefileを出力するシェルスクリプトが添付する必要があった。

この方法は小さなプロジェクトではうまく回すことができたが、Pythonコミュニティでは、予想を上回るペースで次々と新しい拡張モジュールが開発され始めていたため、コンパイルとロードを分離することができるような拡張モジュールへの期待が高まっていた。その後すぐにプラットフォーム固有の動的リンクAPIをラップしたインタフェースのコードが寄贈されて、".py"ファイルと同じようにimport文が外のディスクにある共有ライブラリを探しに行けるようになった。初めて動的ロードのことがCVSのログに登場したのは1992年の1月であり、1994年の末までにはほとんどすべての主要なプラットフォームがサポートされた。

動的なリンクのサポートはとても役に立つことが分かったが、それと同時にメンテナンスの悪夢に悩まされることになった。プラットフォームごとに異なるAPIを使用しており、何かしらの制約があるプラットフォームも存在した。1995年の1月には、動的リンクのサポートのコードは再構築され、すべての関連するコードは一つのソースファイルに集約された。しかし、結果として、プラットフォームごとの条件付きコンパイル指令(#ifdef)によって雑然とした大きなファイルが作られることになった。1999年の12月にはGreg Stein氏の協力を得て、プラットフォーム固有のコードはそれぞれのプラットフォームおよびプラットフォームのファミリーごとの固有のファイルに分割するという再構築を再度行った。

Pythonにおける動的ロード可能モジュールのサポートはこのように進展があったが、多くのユーザにとってはそのようなモジュールをビルドする手順というのは不可解なものとして残ってしまっていた。モジュールをビルドするユーザ数は大きく増えてきた。特に、SWIGのような拡張ビルドツールの導入もこれを後押しした。しかし、拡張モジュールを配布しようとするユーザは、プラットフォーム、コンパイラ、リンカのすべての考えられる組み合わせ上で、モジュールをコンパイルするという大きなハードルに直面した。最悪のケースのシナリオは、ユーザがそれぞれ、コンパイラとリンカに渡す正しい設定を書いたMakefileとコンフィグスクリプトを作成する必要があるというものである。あるいは、ユーザが自分の拡張モジュールの情報を、Python自身のMakefileに追加し、Pythonの部分再ビルドを行って、正しいオプションでモジュールのコンパイルを行う必要があった。しかし、この方法は、エンドユーザがPythonのソースファイルを手に入れなければならないのが問題である。

最終的には、distutilsと呼ばれるPython拡張のビルドツールが考案されて、どの環境でもPythonの拡張モジュールのビルドしてインストールできるようになった。Pythonのmakefileによって、必要なコンパイラとリンカのオプションはデータファイルに書き込まれるようになった。このファイルはdistutilsが拡張モジュールをビルドする際に参照される。distutilsのほとんどの部分はGreg Ward氏によって作成された。古いPythonのバージョンをサポートするためにdistutilsの最初のバージョンはPythonとは別に配布されたが、Python 1.6からは標準ライブラリのモジュールとしてPythonの配布物に統合された。

distutilsには他にも注目すべき点がある。ただC言語のソースコードから拡張モジュールをビルドするだけにとどまらず、様々なことができるようになっている。ピュアPythonのモジュールやパッケージのインストールにも使用することができるし、Windowsの実行可能なインストーラを作成したり、SWIGのようなサードパーティの作成したツールを実行することも可能である。しかし、多くの人は複雑で、メンテナンスをするのも苦労するようなdistutilsに悩むようになった。そのため、最近ではサードパーティによる代替策("eggs"の別名でも知られるez_install)も人気になりつつあるが、同様に、動かないという苦情が出たりして、開発コミュニティの分断を引き起こしてしまっている。この拡張モジュールのビルドを完全に一般化をするという課題は、本質的に難しい問題である。

※訳注:distutilsのメンテナンスの苦労という話が出ているが、ez_installが備えている、パッケージ名を指定してウェブサイトからダウンロードしてインストールしたり、関連するモジュールを一緒にインストールしたり、適切なバイナリパッケージを自動選択したり、アップデートを行ったり、といったパッケージマネージャ的機能がなく、ソースコードからのクリーンインストールしかできないことを言っていると思われる。