2010年9月21日火曜日

なぜPythonの割り算はC言語と違う方式なのか?

今日は何度も「なぜPythonの整数の割り算はC言語のようにゼロに向けて丸めるのではなく、切り捨てなのか?」と聞かれた。

数値が両方とも正の場合には、何も驚くようなことは発生しない。

>>> 5//2
2

しかし、どちらかの数値が負の場合、結果は切り捨てられる。言い換えると、ゼロから遠い方向(負の無限大方向)に向けて丸められる。

>>> -5//2
-3
>>> 5//-2
-3

この挙動は何人かの人を混乱させたようであるが、このような実装になっているのは数学的な根拠がある。整数の割り算の演算子(//)と、その弟の余りを計算する演算子(%)は、次のような数学的な関係を保っている。すべての変数は整数とする。

a/b = q あまり r

これは、次のような関係性を持っている。

b * q + r = a かつ 0 <= r < b
(ただし、a, bはともに0以上の整数)

もしも、この式の関係性を、負の数値まで拡張したいのであれば、2つの選択肢があります。もし商をゼロに向けて丸めるのであれば、付帯条件を0 <= abs(r)と変更する必要がある。それ以外には、qを負の無限大の方に向けて丸める方法がある。この場合、付帯条件は0 <= r < bのままである。

数学の整数論を考えると、数学者はいつも後者の選択を望ましいと考えてきた(Wikipedia参照)。この方式の割り算が役に立つ、おもしろい利用方法がいくつかあったので、Pythonでもこの後者の挙動を採用した。POSIXのタイムスタンプ(1970年からの経過の秒数)を取得して、時刻に変える、という場面を考えてみる。1日は24時間なので、24 * 3600 = 86400秒ある。これを計算するには、単にt % 86400とやれば良い。しかし、1970年依存のより前の時刻を計算したいと考えると、ゼロ方向に丸めてしまうと、無意味な結果が帰ってくることになる。負の無限大方向に丸めるというルールであれば、このような場面でも、そのまま適用できる。

コンピュータグラフィックスで、ピクセルの位置を計算する場面でも、同じ事が言える。こちらの方が良く発生するだろう。

もしbが負の値をであれば、すべてが逆さまになる。普遍条件は次のようになる。

0 >= r > b.

それでは、なぜC言語はこのような実装になっていないのだろうか?それは、Cが設計された当時のハードウェアがこのようにしていなかったからである。そして、現在では負の数は補数として表現されているが、当時のコンピュータは少なくとも整数に関しては、「符号+大きさ」という表現になっていた。私が最初に触れたコンピュータはデータを制御するメインフレームであったが、浮動小数点数と同じく、整数に補数を使うようになっていた。そのハードは負のゼロを表すパターンが60個もあったのである!

Pythonの浮動小数点数を完全に知り尽くしているTim Petersは、これらの規則を浮動小数点数の割り算にも広げたいという私の願望に対して、いくつか心配に思っていることを説明してくれた。彼の心配は正しかった。負の無限大の方向に丸めるというルールは、x%1.0という式があったときに、xが非常に小さい負の整数の時に精度が失われてしまうのである。しかし、これは整数の割り算を破壊するものではなく、//はそれに密に結合されている。

PS. 上記の説明では、/の代わりに//を使用した。これはPython 3の文法で、Python 2でも割り算が整数領域で行われるのを強調したいときに利用できる。Python 2の/演算子は、演算する数が両方整数か、一つ以上浮動小数点数が含まれているかで型が変わるという曖昧なところがある。しかし、これに関してはまったく別の話なので、ここでは説明しない。詳しくはPEP 238に書かれている。

2010年9月8日水曜日

リスト内包表記〜ジェネレータ式

リスト内包表記はPython 2.0で追加された。この機能はGreg Ewingによるパッチを元にして、Skip MontanaroとThomas Woutersらの貢献もあって実現された。私の記憶が正しければ、Tim Petersもこのアイディアをしっかりと保証してくれた。基本的には、リスト内包表記は数学者によって使用されてきた、よく知られた表記法をPythonicに解釈して実現したものである。

{x | x > 10}

これは、一般に、10より大きな数の集合と解釈される。数学の世界では、この形式は、例えば全ての実数、全ての整数などを表す普遍集合(universal set)であると、読み手は解釈する。Pythonの2.0には普遍集合の概念はなく、setもなかった。setについてもおもしろい話があるため、将来ブログに投稿することになるだろう。

この概念や、他の状況も踏まえ、次のようなPython流の表記が作られた:

[f(x) for x in S if P(x)]

これを実行すると、シーケンスSに含まれる値のうち、述部Pの評価結果に合う要素に対して、関数fを適用した結果を含むリストが生成される。if節はオプションとなっているし、また、for節を複数入れて、それぞれにif節を書いて、ネストされたループを表現することもできる。ただし、このネストされたループはあまり使われることはない。一般的には多次元の要素を、1次元のリストにマップすることが良く行われる。

リスト内包表記は、組み込み関数のmap()filter()の代替手段となっている。map(f, S)は、[f(x) for x in S]と同じ意味となるし、filter(P, S)[x for x in S if P(x)]と同じ意味となる。map()filter()を使った表現の方がコンパクトであるため、リスト内包表記の方が推奨されていないのでは?と思う人もいるだろう。しかし、より現実的なサンプルを見ると見解が変わるだろう。与えられたリストの全ての要素に数字の1が足された、新しいリストを作りたいとする。リスト内包表記を使った場合には、[x+1 for x in S]と表現できる。map()を使うと、map(lambda x: x+1, S)となる。"lambda x: x+1"はインラインで無名関数を作るPythonの書き方である。

ここでの本当の問題はPythonのラムダ記法が冗長すぎることで、この表記がもっと簡潔になればmap()を使った記法がより魅力的になるはずだ、ということがずっと主張されてきた。私個人の見解としてはこれには反対である。リスト内包表記の方が、特にマップされる式の複雑さが増加するとmap()を使った関数表記よりも見やすくなることが分かったからである。それに加えて、リスト内包表記を使った方が、実行速度の面からも、map()とラムダを使うよりも高速である。内部的に、ラムダ関数の呼び出し時には新しいスタックフレームが作られるオーバーヘッドがあるが、リスト内包表記の評価時には新しいスタックフレームが作られないからである。

リスト内包表記の成功を受けて、Python 2.4からは似たような表記であるが、実際にリストを生成せずにシーケンスを表現できる、ジェネレータ(これについては将来のエントリーで紹介する)が発明されることになった。この機能は「ジェネレータ式」と呼ばれる。

sum(x**2 for x in range(1, 11))

これは、引数にジェネレータ表現の引数を使用して、組み込みのsum()関数が呼ばれている。このジェネレータは、1から10までの数値の数の2乗を生成する。この関数は引数で生成された値をすべて合計して、385を返す。sum([x**2 for x in range(1, 11)])という表記と比べると、メリットは明らかである。リスト内包表記を使うと、すべての2乗の数を含むリストが実際に生成され、一度使われて破棄されてしまうのである。メモリ使用量が大きくなる巨大な配列の場合には、これも考慮すべきである。

リスト内包表記と、ジェネレータ表現の違いは小さいということも説明すべきだろう。例えば、Python 2では、次の表現は有効なリスト内包表記である。

[x**2 for x in 1, 2, 3]

しかし、次の表現はジェネレータ表現としては有効ではない。

(x**2 for x in 1, 2, 3)

"1, 2, 3"の部分にカッコを付けることで、きちんと動作させることができる。

(x**2 for x in (1, 2, 3))

Python 3では、これらのカッコは、リスト内包表記でも付ける必要がある。

[x**2 for x in (1, 2, 3)]

しかし、通常のforループや、明示的なforループの場合にはこれをキャンセルすることもできる。

for x in 1, 2, 3: print(x**2)

なぜ違いがあるのか、なぜPython 3.0ではリスト内包表記が厳格化されたのか?設計に影響を与えた要因としては、後方互換性、あいまいさの排除、等価性、言語の進化がある。初めPythonには、明示的なforループしかなかった。この場合には、'in'の後ろから、コロン(:)の前までであったため、あいまいなところはなかった。そのため、既知の値の上でループを回したいのであれば、煩わしくカッコを付ける必要はない。これを見ると、次のように書けた、Algol-60が思い出される。

for i := 1, 2, 3 do Statement

Algol-60はそれぞれの式の中に、step-until構文を利用することもできる。

for i := 1 step 1 until 10, 12 step 2 until
50, 55 step 5 until 100 do Statement

(これを振り返ると、Pythonのforループも、複数のシーケンス上でループが回せていたらもっとクールだっただろう)

Python 2.0にリスト内包表記を追加したときにも同じような推測法を適用した。シーケンスの式の後ろには閉じたブラケット']'、もしくは'for', 'if'キーワードのみが来るため問題が発生することはなかった。

しかし、ジェネレータ式が2.4で追加されると、あいまいさの問題が頭をもたげてきた。ジェネレータ式のまわりのカッコは、ジェネレータ式の一部ではないことも技術的にありえるからだ。次の例を見てみよう。

sum(x**2 for x in range(10))

外部のカッコは、sum()関数の呼び出しの一部であり、「裸の」ジェネレータ式が最初の引数として渡される。そのため、理論的には次の式は2通りの理解の仕方がある。

sum(x**2 for x in a, b)

これは、次の文を意図したものである。

sum(x**2 for x in (a, b))

このようにも理解できる。

sum((x**2 for x in a), b)

その後、活発な議論が行われ、(私の記憶が正しければ)このような場合には推測をしない、ジェネレータ内包表記の場合には、'in'キーワードの後ろには単一の式(もちろん繰り返し可能)を必要とする、ということになった。既に人気のある構文となっていた、リスト内包表記を使用していた既存のコードの互換性を崩したくない、という考えもあった。

Python 3の設計をしている時にリスト内包表記の動作に変更を加えた。次のようなリスト内包表記があったとすると、

[f(x) for x in S if P(x)]

組み込みのlist()関数をジェネレータ式に適用した、次の文と同一になるようにした。

list(f(x) for x in S if P(x))

このため、リスト内包表記でも、多少制約の強い、ジェネレータ式の構文を使用することを決定した。

Python 3ではもう一つ変更を加え、リスト内包表記とジェネレータ式の等価性が向上した。Python 2では、リスト内包表記のループ制御変数は周辺のスコープに「漏れ」ていた。

x = 'before'
a = [x for x in 1, 2, 3]

print x # this prints '3', not 'before'

これは、リスト内包表記の元本の実装によってもたらされた副作用である。これは昔からある、Pythonの「隠しておきたい小さな汚点」の一つであった。意図的な妥協のおかげで、リスト内包表記は最初から高速な実装であった。初心者がひっかかるような落とし穴ではなかったが、時々害を受ける人が出た。ジェネレータ式の実行には別の実行フレームが必要とされるため、ジェネレータ式では同じようなことはできなかった。そのため、特に小さなシーケンスを相手にする場合には、効率性の面でジェネレータ式はリスト内包表記に負けていた。

しかし、Python 3では、我々はジェネレータ式と同じ実装の戦略を取ることで、リスト内包表記の「隠しておきたい小さな汚点」を修正することを決定した。そのため、Python 3で上記のサンプルを実行すると(print(x)と変更を加えましょう :-)、'before'と表示され、リスト内包表記で一時的に隠された変数'x'も、周囲のスコープの'x'を上書きしないということが分かるだろう。

これによって、Python 3のリスト内包表記は遅くなったのではないか、と心配する人もいると思うが、Python 3を高速化しようという膨大な努力のおかげで、Python 3のリスト内包表記と、ジェネレータ式は、Python 2と比較してそれぞれ高速になっている。また、この2つの間にあった速度差も解消している。

更新: Python 3で追加された、setとdictの内包表記について言及するのを忘れていた。これらは、リスト内包表記を素直に拡張しただけである。

2010年8月1日日曜日

メソッド解決順序(MRO)

多重継承が利用可能な言語では、メソッドを探索するときのベースクラスを探索する順序を、メソッド解決順序(MRO)と呼んでいる。Pythonではメソッドだけではなく、属性の探索でもこれが利用される。単一継承しかサポートしていない言語では、MROはとてもつまらない話題であるが、多重継承の場合には必要となってきて、MROアルゴリズムの選択が極めて難しい問題となりうる。Pythonでは、クラシック、Python 2.2の新スタイル、Python 2.3の新スタイル(C3とも呼ばれる)の3種類のMROアルゴリズムを持っていることが知られている。Python 3では、最後のアルゴリズムだけが生き残っている。

旧スタイルクラスはシンプルなMROの方式を利用していた。メソッドを探す場合には、シンプルに深さ優先探索を行って、ベースクラスの中で最初にマッチしたオブジェクトが返される。例えば、次のようなクラス構造について見てみよう。

class A:
  def save(self): pass

class B(A): pass

class C:
  def save(self): pass

class D(B, C): pass

クラスDのインスタンスxを作ったとすると、クラシック方式のメソッド解決順序では、D, B, A, Cという順序でクラスが探索されることとなる。そのため、x.save()というメソッド呼び出しが行われると、C.save()ではなく、A.save()が実行される。この方式は単純なケースではうまく行くが、より複雑なケースでは問題が発生してくる。次のような、ダイアモンド継承下でのメソッド検索の問題について見てみよう。

class A:
  def save(self): pass

class B(A): pass

class C(A):
  def save(self): pass

class D(B, C): pass

ここでは、クラスDはBとCを継承しており、その両方ともがクラスAを継承している。クラシックMROを使うと、D, B, A, C, Aという順序でメソッドを探索していくため、x.save()は、A.save()を参照する。しかし、この場合は、あなたが期待するのと違っているはずだ。BとCの両方がAを継承しており、再定義されてクラスAよりも詳細化されたC.save()を、クラスAのメソッドよりも呼びたいと考える人もいるだろう。実際はA.save()が常に呼ばれる。例えば、save()メソッドがオブジェクトの状態の保存に使用されるのであれば、クラスCによって定義されたメソッドが呼ばれないことで、Cに関する状態が無視されてプログラムの動作に問題が生じる。

この種の多重継承は、既存のコードの中でもめったに使われていないが、新スタイルクラスを使うとこの問題が日常茶飯事となる。新スタイルクラスはベースクラスであるobjectを継承することで行われる。新スタイルクラスでは、どのように多重継承を行っても、必ず上記のようなダイアモンド継承状態になる。

class B(object): pass

class C(object):
  def __setattr__(self, name, value): pass

class D(B, C): pass

それに加えて、派生クラスで拡張されるような(__setattr__()など)、いくつかのメソッドをobjectクラスが持っているため、このメソッドの解決順序の重要性は以前よりも高くなった。例えば、上記のコードの場合には、C.__setattr__は、クラスDのインスタンスにも適用されるべきである。

Python 2.2では、新スタイルクラスの導入のためにメソッド解決順序の見直しを行い、クラス定義時にあらかじめ順序を計算しておき、クラスオブジェクトごとに属性として格納するという方式のMROを適用した。以前の公式ドキュメントでは、深さ優先探索のMROを利用していると書かれていた。この検索順序の中で同じクラスが重複すると、MROのリストの中で後半に出てきた方が削除されるようになった。そのため、前に挙げたサンプルでは、D, B, C, Aという順序になり、以前のクラシッククラスで説明したD, B, A, C, Aとは違う結果になる。

実際には、MROの計算はこれよりもはるかに複雑である。私は、この新しいMROアルゴリズムがうまく働かないようなケースをいくつか発見した。2つのベースクラス(A, B)が、2つの異なる派生クラス(X, Y)のMROのリストの中でそれぞれ異なる順序で並ぶという特殊なケースがある。これらのクラスは元のクラスをそれぞれ異なる順序で継承しており、さらにそれを他のクラス(Z)が継承している。

class A:
class A(object): pass
class B(object): pass
class X(A, B): pass
class Y(B, A): pass
class Z(X, Y): pass

試しにこの新しいMROアルゴリズムを使用してみると、これらのクラスのMROは、Z, X, Y, B, A, objectという順序に並ぶ。objectというのは、共通のベースクラスである。しかし、私はBとAが逆になっているというのが気に入らなかった。本当のMROは、それらの順番を入れ替えて、Z, X, Y, A, B, objectという順番にする。このアルゴリズムは最初に検索したときに見つかった順番をなるべく保存しようとする。クラスZの場合には、継承リストの順番が先ということで、ベースクラスXが先に検索される。XはAとBを継承しているため、MROアルゴリズムはこの順番を保存しようとする。これはPython 2.2で実装されたもので、初期のアルゴリズムとしてドキュメント化した。

しかし、Python 2.2で新スタイルクラスが導入されてすぐに、Samuel Pedroni氏は、このドキュメント化されたMROアルゴリズムと、実際にコードを動かした結果が異なるということを発見した。さらに、これらの矛盾は上記のような特別な場合以外でも発生していた。長い時間をかけて議論を行い、Python 2.2で採用されたMROは壊れていることが確認され、"A Monotonic Superclass Linearization for Dylan" (K. Barrett, et al, presented at OOPSLA'96) で説明されている、C3線形化アルゴリズムを採用することが決定された。

本質的には、Python 2.2のMROアルゴリズムの本質的な問題は、探索順を線形に並べるという問題に関係していた。継承階層が複雑になると、それぞれの継承の関係は、シンプルなルールでクラスがどのような順番に並ぶかのチェックが行われる。明らかに、クラスAがクラスBを継承している場合にはMROはAがBの前にあることをチェックしなければならない。同様に、クラスBがクラスCとDを多重継承している場合には、BはCの前にあり、CはDの前にあることがチェックされなければならない。

複雑な継承階層の中では、順番を線形化する場合に、これらのルールをすべて満足するようにしたいと思うだろう。つまり、クラスAがクラスBの前になければならないということが決められた場合に、クラスBの方がクラスAの前に来るという矛盾した状態にはなって欲しくはない。その場合には結果が未定義なので、そのような継承階層はリジェクトされなければならない。オリジナルのMROが間違っていて、C3アルゴリズムがうまくいくのはこのような場面である。基本的に、C3の背後にあるアイディアは、複雑なクラス継承を行ったときにも、継承の関係から作られた順番のルールをすべてリストアップした場合に、すべてのクラス間でこれらのルールを満足するようなクラスの順番に、一列に並べることができるというものである。もしも、順番が一意に決まらない場合には、このアルゴリズムはその継承をエラーとして失敗させるようになっている。

そのため、Python 2.3では、Python 2.2で自前作ったMROアルゴリズムを捨て、アカデミックな目でチェックされたC3アルゴリズムを選択することにした。これにより、矛盾した順序を持つ継承を行うと、Pythonがそれをエラーとするようになった。例えば、先ほどの例で言えば、クラスXとクラスYの間では、順序に矛盾がある。クラスXを見るとクラスBの前にクラスAがなければならないということになるが、クラスYを見ると、クラスAの前にクラスBがなければならないことになっている。個別に見れば、これらの矛盾は問題ないが、これらのクラスXとYが、同じ継承階層の中に現れる(例えば、ここではクラスZが定義されることによってこれが発生する)と、C3アルゴリズムはこのクラスをリジェクトする。これは言うまでもなく、Pythonのthe Zen of Pythonの「問題があるときには、こっそりと処理してはならない」というルールにもマッチしている。

2010年7月17日土曜日

import antigravity

原文:http://python-history.blogspot.com/2010/06/import-antigravity.html
原文投稿者:Guido van Rossum

Pythonについて説明をしているXKCDコミックを参照するというantigravityモジュールが、Skip MontanaroによってPython 3に追加された。私が知る限り最初にこのモジュールについて焦点をあてたブログはこれである: http://sciyoshi.com/blog/2008/dec/30/import-antigravity/

しかし、このモジュールの本当の起源はGoogle App Engineである。私たち(訳注: GuidoはGoogleで働いている)がApp Engineを公開した、2008年4月7日の直前だった。公開の数週間前の、ほとんどのコードがコードフリーズしたときに、GoogleのApp Engineチームは何かイースターエッグを忍び込ませたいと考えた。時には複雑で、時にはあいまいで、時にはリスキーな、何度にも渡るすばらしい議論の末に、"antigravity"モジュールが選択された。App Engine版はPython 3版よりも多少がんばった作りになっている。App Engine版では、fly()関数が定義されていて、次の2つのうちの一方の動作をランダムにランダムに行う。1つ目は10%の確率で発生するモノで、XKCDのコミックにリダイレクトでジャンプする。もう一方はHTML内に、テキスト版のコミックを単純に書き出すだけのものである(最後の行にコミックへのリンクが表示される)。次のようなmain()プログラムを作成すると、App Engineのアプリケーション内でこれを呼び出すことができる。

import antigravity

def main():
    antigravity.fly()

if __name__ == '__main__':
    main()

[更新情報] Python 3の標準ライブラリ版では、ソースコードの中を見てみると、イースターエッグの中にさらにイースターエッグが仕込まれている。XKCDのgeohashアルゴリズムについて説明している関数が定義されている。

2010年7月13日火曜日

import thisとThe Zen of Python(2)

原文:http://www.wefearchange.org/2010/06/import-this-and-zen-of-python.html
原文投稿者:Barry Warsaw

注:本エントリーは、Guidoのブログではなく、そこで紹介されていた、Barry Warsaw氏のブログの翻訳です。翻訳と公開の許可をしてくださったBarry Warsaw氏に感謝します

Richard Jones氏は現在(元記事執筆時点)、PyCon Australiaでする話の準備をしている。彼はその準備の中で、私に"Zen of Python"の歴史についてたずねてきた。それはTim PetersがPython界に永遠に残した言葉で、Pythonの本質的な真実を良く伝えるものであり、さまざまな場面で引用されている。最初に調べたときには、このリストが最初に公表された時の情報を探すことはできなかったが、その後、自分のメールのアーカイブをすみずみまで調べたところ、"Zen of Python"が最初にPython-listというメーリングリストに投稿された時のメールを見つけることができた。"The Python Way"というタイトルで、1999年の7月4日に投稿されたものであった。

すぐに最初の出典を見つけることができなかったので、自分のアーカイブの探査に戻ったり、"this"モジュールの解析を行っていた。最近のPythonのインタプリタでは、次のようにタイプすると、"Zen of Python"を読むことができるというのをご存知だろうか?(訳注:日本語訳はこちら)

% python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

"import this"の背景の話は楽しいものなので、私は、これが追加された経緯について紹介したら面白いだろうと考えた。おそらく、Guidoの"History of Python"というブログに何かストーリーを追加するような内容になるはずである。

2001年の秋に、ForetecInternational Python Conference #10 (IPC 10, PyConの先駆けとなるイベント)の開催準備をしていた。ForetecはCNRIの子会社のイベント運営会社で、Pythonlabsの設立のために退社する2000年まで、Guido, Fred,Jeremyと私を雇っていた。その会社は動きが悪いこともあったが、好意的な面も多数ある会社であった。その時には既に、私たちは友人たちと一緒にZope Corporation(おそらく当時はまだDigital Creationsという名前)で働いていたが、ForetecはIPCの開催を継続していた。ForetecはカンファレンスのスローガンをTシャツにプリントしたいと考えており、Pythonコミュニティからスローガンの提案を集めようと考えていた。提案されたエントリーを審査して、勝者を決めるというタスクを行うことをPythonlabsは同意した。しかし、私の記憶では、当時奥さんの出産を控えていたGuidoには、エントリーを審査する時間とエネルギーがなかった。

私たちは500ほどのエントリーを受けつけたが、ほとんどはひどいものであった。Timは「Louise、5分も見ていると、脳が溶け出しそうになるよ」というようなことを言っていたが、なんとか作業を継続して、130エントリーほどになるまでカットした。最後の勝者が決まる、最後の1分まで、私たちはただただ気が狂いそうだった。Timは私に、リストを交換する提案をしました。リストを半分にして、半分だけ交換しました。Timは私よりも遥かに数学が得意であり、Pythonの数値の割り算のくせを忘れていたため、最後の2つのエントリーが抜けてしまった。それらが「好きなものはすべて食べられます。咀嚼するのはオプションです」(私には良いところがまったくないと思われた;)と、「import this」であった。Timは「Pythonのプログラムに関して学ぼう」というエントリーについて二人ともが良いと思っていた土壇場になって、このおもしろいものを復活させた。

私はこの「import this」という偉そうなセンテンスを気に入った。また、このセンテンスを利用すればマイケル・ジャクソン風のすばらしいTシャツが作れそうだという可能性を感じた。

"import this"を選択してすぐに、それをプログラムとして実装した方がいい、ということに気づいた。Python 2.2のリリースが間近だったため、私は、コードのチェックインを知らせる機能をオフにして"this.py"というthe Zen of Pythonを表示させるモジュールを忍び込ませてはどうか?という提案をした。TimかGuidoのどちらかが、内容を難読化するのにrot13という暗号化をしてみたらどうか?と提案した。その仲間内以外の人には誰にも話しをしなかった。Googleを探索して調べたところ、IPC 10が終了してすぐに、私たちはPython 2.2.1としてthis.pyをコミットして、このイベントを祝していた。つまり、2.2.1には、本来リリースすべき真面目な機能に加えて、このジョークの機能が追加されていたのである。私の記憶が正しければ、ここで仕込んだ小さなイースターエッグが発見されるまでには、多少の時間を要した。

これは、Pythonコミュニティにユーモアがあった時代を思い出させてくれるできごとである。

import thisとThe Zen of Python

原文:http://python-history.blogspot.com/2010/06/import-this-and-zen-of-python.html
原文投稿者:Guido van Rossum

Barry Warsaw氏が、Pythonの歴史の中で、今まで明らかにされていなかった(記録に残したい)事実を紹介する面白いエントリーをブログに投稿している。

http://www.wefearchange.org/2010/06/import-this-and-zen-of-python.html

2010年7月8日木曜日

新スタイルクラスの内部の話

原文:http://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.html
原文投稿者:Guido van Rossum

[注意:この投稿はとても長くて技術的です]

表面上は、新スタイルクラスは、元々のクラスの実装と非常に似通って見えるが、新スタイルクラスでは次のような数々の新しいコンセプトが導入されている。

  • __new__()という名前の低レベルのコンストラクタ
  • 属性アクセスのカスタマイズを一般的にできるようにするデスクリプタ
  • 静的メソッドとクラスメソッド
  • プロパティ(演算してから結果を返す属性)
  • デコレータ(Python 2.4から導入)
  • スロット
  • 新しいメソッド解決順序(Method Resolution Order, MRO)

このエントリーでは、これらのコンセプトについて光を当てていこうと思う。

__new__()という名前の低レベルのコンストラクタ

クラスを定義するときには通常の場合には、インスタンスの生成後にどのように新しいインスタンスを初期化するのかを定義する、__init__()メソッドを実装していた。しかし、クラスの作者が、インスタンスの生成方法そのものもカスタマイズしたいというケースがいくつかある。例えば、オブジェクトを永続化されたデータベースから復元する場合などである。"new"モジュールなど、普通ではない方法で特定の種類のオブジェクトを作成するためのライブラリはいくつかあったが、旧スタイルクラスではオブジェクトの生成をフックしてカスタマイズする方法が提供されていなかった。

新スタイルクラスでは、__new__()という新しいクラスメソッドが提供されたことで、クラスの作者が新しいクラスのインスタンスを生成する方法もカスタマイズできるようになった。__new__()メソッドをオーバーライドして、以前作成したインスタンスを返すことで、クラス作者はシングルトンパターンのようなパターンも実装することが可能である。また、他のクラスのインスタンスも返すことができる。しかし、__new__()の活用法には、これ以外にももっと重要なものがある。例えば、pickleモジュールでは、__new__はシリアライズされたオブジェクトを復元してインスタンスを作成するのに使用されている。このケースでは、インスタンスは生成されるが、__init__()メソッドは呼び出されないという実装になっている。

他にも、変更不可(immutable)型のサブクラス化を行う際にも、__new__が役に立つ。変更不可能という特性上、標準的な__init__()メソッドでは、このようなオブジェクトを初期化することはできない。そのため、オブジェクトの作成時に何か特別な初期化処理を行う必要がある。もし、変更不可能なオブジェクトの中に格納される値を変更したい場合には、ベースクラスの__new__メソッドに変更後の値を渡すことによって、このような処理を行うのに利用できる。

デスクリプタ

デスクリプタは、旧スタイルクラスの実装の中心となっている、束縛メソッドの概念を一般化したものである。旧式のクラスでは、インスタンス属性がインスタンス辞書の中から見つけられない場合には、クラス辞書を引き続き探索し、その後はベースクラスのクラス辞書を再帰的にたどっていくという振る舞いをする。もし属性がクラス辞書の中で見つかると、インスタンス辞書とは異なり、インタプリタが見つかったオブジェクトがPythonの関数オブジェクトであるかどうかがチェックされる。もし、そのオブジェクトが関数オブジェクトだった場合には、見つけたオブジェクトを返値にするのではなく、カリー化関数のように振る舞うラッパーオブジェクトを返す。ラッパーが呼ばれると、インスタンスを引数リストの先頭に挿入してから、オリジナルの関数が呼び出される。

例えば、クラスCのインスタンスxがあると想定しよう。今、このインスタンスに対して、x.f(0)という呼び出しが行われたとする。この操作を分解すると、まず、"f"という名前の属性をインスタンスxから探しだし、引数として0を渡して呼び出される。もし、"f"がクラスの中で定義されたメソッドと一致するのであれば、次の擬似コードの関数のような振る舞いをするラッパー関数を属性として返す:

def bound_f(arg):
    return f(x, arg)

もしも引数の0とともにこのラッパーが呼ばれると、このラッパー関数はx0という二つの引数をともなって"f"を呼び出す。これが、クラスのメソッドが"self"引数を取得する基本的なメカニズムである。

(ラップされていない)関数オブジェクトfにアクセスする別の方法としては、クラスCの属性として"f"という名前の属性の問い合わせをするというものがある。このような検索を行うと、ラッパーのつかない、関数fを単純に返す。言い換えると、x.f(0)は、C.f(x, 0)と同じということである。Pythonの中では、これらの呼び出しは基本的に等価である。

旧式のクラスでは、属性を調べて、他の種類のオブジェクトが見つかった場合には、ラッパーは生成されずに、クラス辞書の中から見つかったオブジェクトがそのまま返される。これにより、クラス属性をインスタンス変数の"デフォルト"値として利用することもできる。例えば、上記の例であれば、もしクラスCが"a"という属性名で数値の1を持っていて、xのインスタンス辞書に"a"というキーがなければ、x.a1となる。x.aに割り当てると、xのインスタンス辞書に"a"というキーができ、属性辞書の探索順序の影響でクラス属性の値が隠蔽される。x.aを削除すると、隠されていた値(1)に再びアクセスできるようになる。

残念ながら、何人かのPython開発者により、この実装の限界が発見されてしまった。限界の一つ目が、いくつかのメソッドPythonで実装し、他のメソッドをCで実装するという"ハイブリッド"クラスを実装することができないというものである。これはPythonの関数だけが上記のような方法でインスタンスにアクセスするためのメソッドを提供していたのと、この振る舞いが言語にハードコードされていたのが原因である。また、C++やJavaプログラマーが親しんでいる、静的メンバー関数のような異なる種類のメソッドを定義する方法もなかった。

この問題に対処するために、Python 2.2からは、上記のラッピングの振る舞いを素直に一般化した仕組みが導入された。Pythonの関数オブジェクトのみをラップするというハードコーディングされた振る舞いの代わりに、属性検索で見つかったオブジェクト(上記の例だと関数f)ごとにラッピングするようになった。もしオブジェクトが見つかると、__get__と呼ばれる特殊なメソッド名を持つ、「デスクリプタ」と呼ばれるオブジェクトが返される。次に__get__メソッドが呼ばれ、属性検索の結果として、このメソッドの返値が使用される。もしオブジェクトが__get__メソッドを持っていない場合には、それがそのまま返される。インスタンス属性検索コード内に特別な処理を行う関数オブジェクトを作らずに、関数オブジェクトをラッピングして返すという今まで通りの振る舞いにするために、関数オブジェクトには、以前のコードと同じようなラッパーを返す、__get__メソッドが追加された。このデフォルトのラッパー以外にも、ユーザが自由に__get__というメソッドを持つ他のクラスを定義し、インスタンス属性検索中でクラス辞書の中から発見された場合に、好きなようにラッピングすることもできるようになった。

属性検索コンセプトを一般化するのに加え、属性の設定と削除の操作にも、このアイディアを拡大して適用した。x.a = 1del x.aなど、今までと同じような割り当て操作を使用することができる。このような操作が行われた時に、"a"という属性がインスタンス辞書ではなく、インスタンスのクラス辞書の中で発見された時に、クラス辞書に保持されたオブジェクトに__set____delete__という特別メソッドがないかどうかチェックされる。(__del__というメソッドはまったく別の意味で既に使用されている。そのため、これらのメソッドを再定義することによって、デスクリプタオブジェクト属性の取得、設定、削除の操作の時にどのような処理が行われるかを、完全に制御することができる。しかし、デスクリプタインスタンスが、インスタンス辞書ではなく、クラス辞書内に設定された時にだけ適用されるという点は、重要なので最後にもう一度強調しておきたい。

staticmethod, classmethod, property

Python 2.2からは、classmethod, staticmethod, propertyという、新しいデスクリプタのメカニズムに関連する、3つのクラスが追加された。classmethodstaticmethodは、関数オブジェクトに関するシンプルなラッパで、保持している関数オブジェクトの呼び出し方が通常とは異なるラッパーを返す、__get__メソッドが実装されている。例えば、staticmethodのラッパーは、引数リストに変更をいっさい加えずに関数を呼び出す。classmethodのラッパーは、インスタンスそのものではなく、インスタンスのクラスを引数の先頭に追加してから関数を呼び出す。呼び出されるのがインスタンス経由であっても、クラス経由であっても、それぞれの引数は一緒となる。

propertyクラスは、"属性"に対する値の設定と、"属性"からの読み込みに関する対となる2つのメソッドを持つようなラッパーを生成する。例えば次のようなクラスがあったとする。

class C(object):
   def set_x(self, value):
       self.__x = value
   def get_x(self):
       return self.__x

propertyラッパーを使うと、属性"x"にアクセスされたときに、値の読み込みと設定に、ここで定義されたget_x, set_xメソッドが暗黙的に呼び出されるようにすることができる。

最初にclassmethod, staticmethod, propertyが導入されたときには、これらを簡単に扱える、特別な文法がなかった。そのときは、新しい文法(常に激しい議論が巻き起こる)と一緒に新しい機能を導入しようとすると、議論が収束しなくなって、導入できなくなると考えられたので、機能の追加だけが行われた。そのため、この機能を使用する場合には、通常通りクラスとメソッドを定義したあとに、メソッドをラップするための追加の文を追加する必要があった。

class C:
   def foo(cls, arg):
       ...
   foo = classmethod(foo)
   def bar(arg):
       ...
   bar = staticmethod(bar)

プロパティについても、同様の作法に従っていた。

class C:
 def set_x(self, value):
    self.__x = value
 def get_x(self):
    return self.__x
 x = property(get_x, set_x)
デコレータ

デスクリプタのやり方の不都合な点は、メソッド定義の最後まで読まないと、そのメソッドが静的メソッドが、クラスメソッドか、あるいはその他のユーザ定義の属性を持つメソッドか特定できないという点である。Python 2.4からは、最後に新しい文法が導入され、次のように書くことができるようになった。

class C:
 @classmethod
 def foo(cls, arg):
    ...
 @staticmethod
 def bar(arg):
    ...

@式 というのを、関数定義の前の行に書くことができるようになった。この構文を、デコレータと呼ぶ。__get__を実装したラッパーを作成するデスクリプタと混同しないようにして欲しい。デコレータ構文の文法(Javaのアノテーションから派生)に関しての議論は、「BDFL宣告」によって文法が決定されるまで、延々と続いた。(David BeazleyはBDFLと言う用語の歴史に関しては、私が書いたものとは別々に書いている)。

デコレータ機能は、言語の機能の中で、もっとも成功したものとなった。「うまくいけばここまで広がるだろう」と予想していた範囲いっぱいまで、幅広くカスタムデコレータが使用された。特にウェブフレームワークはこの文法の使用方法について、さまざまな発見をして応用された。この成功を受けて、Python 2.6からは、この文法は関数定義だけではなく、クラス定義でも使用できるように拡張された。

スロット

デスクリプタの導入によって可能になった別の拡張機能としては、クラスの__slots__属性がある。例えば、次のようにクラス宣言が行える

class C:
 __slots__ = ['x','y']
 ...

スロットが定義されると、いくつかのことが行われる。まず最初に、リストに定義されたのと同じ名前の属性しか、オブジェクトに定義できないように制限される。次に、属性が固定されると、それ以上はインスタンス辞書に属性を保持する必要がなくなるため、__dict__属性が削除される。ただし、ベースクラスにはそれがあり、__slots__を利用しないサブクラスで利用される。__dict__の代わりに、配列を使用して、予約された場所に属性が保存される。そのため、すべてのスロットの属性は、それぞれの属性が格納される配列のインデックスを知っている、デスクリプタのオブジェクトが割り当てられる。この機能の実装は、完全にC言語で構築されているため、とても効率が良い。

中には、__slots__を導入した目的が、属性名を制限することによるコードの安全性の向上であると誤解している人もいる。実際には、私の究極の目標はパフォーマンスであった。導入の動機としては、__slots__はデスクリプタの面白い応用例であったから、というのもあったが、これらの新スタイルクラスに導入された変更は、パフォーマンスに対して深刻な影響を与えるのを恐れていたからである。特に、データデスクリプタを適切に動作させるには、オブジェクトの属性に対するあらゆる操作をする前に、データデスクリプタの場合には、まずその属性があるかどうか、クラス辞書を先に見に行く必要がある。その場合には、手動でインスタンス辞書を操作する代わりに、デスクリプタが使用されて属性アクセスが行われる。しかし、このようなチェックを追加する場合には、インスタンス辞書にアクセスする前に、追加の検索が実行されるということを意味している。スロットを利用することでパフォーマンスを向上させることができるため、万が一、新スタイルクラスを導入して、そのようにパフォーマンスの劣化で失望した場合に利用することができる。後になって(パフォーマンスの劣化が思ったよりも少なくて)、不必要であることが分かったが、その時にはもう削除するには遅かった。もちろん、適切に使用すれば、スロットはパフォーマンスを実際に増加させられる。特に、小さいにオブジェクトが大量に作成される場面では、メモリの使用量の削減によって、大きくパフォーマンスは向上する。

次の投稿では、Pythonのメソッド解決順序(MRO)の歴史に触れてみたいと思う。

2010年6月26日土曜日

新スタイルクラス

原文:http://python-history.blogspot.com/2010/06/new-style-classes.html
原文投稿者:Guido van Rossum

以前、Pythonへのクラスの追加は、本質的には後付であったという説明を行った。実装の選択は、Pythonの「手抜きをする」という哲学に従って行われた。しかし、Pythonが進歩するに従い、エキスパートのPythonユーザは、クラスの実装の問題について頻繁に不満を述べるようになってきた。

クラスの実装の1つの問題は、組み込み型のサブクラス化を行う方法がなかったということである。例えば、リスト、辞書、文字列やその他のオブジェクトは何か「特別」なもので、サブクラス化して、一部を特殊化することができなかった。この制限は、「オブジェクト指向である」と主張していた言語にしては、奇妙に見えてしまっていた。

他の問題は、型システムが全般的に、ユーザ定義クラスに関しては「間違っている」ようにしか見えなかったことである。例えば、まったく別の関係ないクラスのユーザ定義の2つのオブジェクトのabがあったとすると、type(a) == type(b)を評価すると、真になってしまうのである。言うまでもなく、C++やJavaなどの、クラスが言語の型システムと密接に結びついているような他の言語に明るい開発者から見ると、かなりおかしく見えた。

Python2.2では、しっかりと時間をとって「正しく動作する」ようにクラスの再実装を行った。この変更は、Pythonのサブシステムをもっとも大々的に書き直したものとなったため、人々からは、この努力に対して「セカンドシステム症候群である」という非難を受ける可能性もあった。この変更では、組み込み型の継承ができないという問題を手っ取り早く修正するだけではなく、真のメタクラスのサポートの追加、多重継承時のメソッド解決順序という難しい問題の修正、その他の多くの機能の追加を行った。この仕事においては、"Putting Metaclasses to Work"という、Ira FormanとScott Danforthが書いた本の影響を強く受けている。この本は、Smalltalkとも違う、メタクラスが何かという示唆を私に提供してくれた。

クラスの書き直しに関しての面白い点は、旧スタイルクラスと置き換えて導入したのではなく、新しい言語機能として新スタイルクラスを導入したということである。後方互換性のために、Python 2のクラス作成プロトコルのデフォルトとして、旧スタイルクラスの実装はそのまま残された。新スタイルクラスを作成するためには、objectクラス(新スタイルクラス階層のルート)などの、既存の新スタイルクラスをただ継承するだけで行うことができる。

class A(object):
  実装
  ...

新スタイルクラスへの変更は大成功であった。新しく導入されたメタクラスは、フレームワーク作成者の中で人気の機能となったし、例外事項が減り、クラスを説明するのが簡単になった。実際、後方互換性は残されているため、新スタイルクラスの導入が行われても、古いコードは同じように動作させ続けることができた。最終的に、旧スタイルは言語から削除されることになると思うが、Pythonユーザは"class MyClass(object)"という、それほど悪くない書き方に、慣れてきている。