x+1"はインラインで無名関数を作るPythonの書き方である。ここでの本当の問題はPythonのラムダ記法が冗長すぎることで、この表記がもっと簡潔になればmap()を使った記法がより魅力的になるはずだ、ということがずっと主張されてきた。私個人の見解としてはこれには反対である。リスト内包表記の方が、特にマップされる式の複雑さが増加するとmap()を使った関数表記よりも見やすくなることが分かったからである。それに加えて、リスト内包表記を使った方が、実行速度の面からも、map()とラムダを使うよりも高速である。内部的に、ラムダ関数の呼び出し時には新しいスタックフレームが作られるオーバーヘッドがあるが、リスト内包表記の評価時には新しいスタックフレームが作られないからである。2.4からは似たような表記であるが、実際にリストを生成せずにシーケンスを表現できる、ジェネレータ(これについては将来のエントリーで紹介する)が発明されることになった。この機能は「ジェネレータ式」と呼ばれる。11))これは、引数にジェネレータ表現の引数を使用して、組み込みのsum()関数が呼ばれている。このジェネレータは、1から10までの数値の数の2乗を生成する。この関数は引数で生成された値をすべて合計して、385を返す。11)])という表記と比べると、メリットは明らかである。リスト内包表記を使うと、すべての2乗の数を含むリストが実際に生成され、一度使われて破棄されてしまうのである。メモリ使用量が大きくなる巨大な配列の場合には、これも考慮すべきである。リスト内包表記と、ジェネレータ表現の違いは小さいということも説明すべきだろう。2では、次の表現は有効なリスト内包表記である。3]しかし、次の表現はジェネレータ表現としては有効ではない。3"の部分にカッコを付けることで、きちんと動作させることができる。3では、これらのカッコは、リスト内包表記でも付ける必要がある。3)]しかし、通常のforループや、明示的なforループの場合にはこれをキャンセルすることもできる。初めPythonには、明示的なforループしかなかった。この場合には、'in'の後ろから、コロン(:)の前までであったため、あいまいなところはなかった。そのため、既知の値の上でループを回したいのであれば、煩わしくカッコを付ける必要はない。これを見ると、次のように書けた、Algol-60が思い出される。StatementAlgol-60はそれぞれの式の中に、step-until構文を利用することもできる。2.0にリスト内包表記を追加したときにも同じような推測法を適用した。'if'キーワードのみが来るため問題が発生することはなかった。ジェネレータ式が2.4で追加されると、あいまいさの問題が頭をもたげてきた。ジェネレータ式のまわりのカッコは、ジェネレータ式の一部ではないことも技術的にありえるからだ。range(10))外部のカッコは、sum()関数の呼び出しの一部であり、「裸の」ジェネレータ式が最初の引数として渡される。そのため、理論的には次の式は2通りの理解の仕方がある。b)これは、次の文を意図したものである。b))このようにも理解できる。b)その後、活発な議論が行われ、(私の記憶が正しければ)このような場合には推測をしない、ジェネレータ内包表記の場合には、'in'キーワードの後ろには単一の式(もちろん繰り返し可能)を必要とする、ということになった。人気のある構文となっていた、リスト内包表記を使用していた既存のコードの互換性を崩したくない、という考えもあった。3の設計をしている時にリスト内包表記の動作に変更を加えた。P(x)]組み込みのlist()関数をジェネレータ式に適用した、次の文と同一になるようにした。P(x))このため、リスト内包表記でも、多少制約の強い、ジェネレータ式の構文を使用することを決定した。3ではもう一つ変更を加え、リスト内包表記とジェネレータ式の等価性が向上した。2では、リスト内包表記のループ制御変数は周辺のスコープに「漏れ」ていた。'before'これは、リスト内包表記の元本の実装によってもたらされた副作用である。これは昔からある、Pythonの「隠しておきたい小さな汚点」の一つであった。意図的な妥協のおかげで、リスト内包表記は最初から高速な実装であった。初心者がひっかかるような落とし穴ではなかったが、時々害を受ける人が出た。ジェネレータ式の実行には別の実行フレームが必要とされるため、ジェネレータ式では同じようなことはできなかった。そのため、特に小さなシーケンスを相手にする場合には、効率性の面でジェネレータ式はリスト内包表記に負けていた。3では、我々はジェネレータ式と同じ実装の戦略を取ることで、リスト内包表記の「隠しておきたい小さな汚点」を修正することを決定した。:-)、'before'と表示され、リスト内包表記で一時的に隠された変数'x'も、周囲のスコープの'x'を上書きしないということが分かるだろう。2と比較してそれぞれ高速になっている。この2つの間にあった速度差も解消している。3で追加された、setとdictの内包表記について言及するのを忘れていた。これらは、リスト内包表記を素直に拡張しただけである。件のコメント:2010年8月1日日曜日メソッド解決順序(MRO)多重継承が利用可能な言語では、メソッドを探索するときのベースクラスを探索する順序を、メソッド解決順序(MRO)と呼んでいる。Pythonではメソッドだけではなく、属性の探索でもこれが利用される。単一継承しかサポートしていない言語では、MROはとてもつまらない話題であるが、多重継承の場合には必要となってきて、MROアルゴリズムの選択が極めて難しい問題となりうる。2.3の新スタイル(C3とも呼ばれる)の3種類のMROアルゴリズムを持っていることが知られている。3では、最後のアルゴリズムだけが生き残っている。旧スタイルクラスはシンプルなMROの方式を利用していた。メソッドを探す場合には、シンプルに深さ優先探索を行って、ベースクラスの中で最初にマッチしたオブジェクトが返される。次のようなクラス構造について見てみよう。Cという順序でクラスが探索されることとなる。そのため、x.save()というメソッド呼び出しが行われると、C.save()ではなく、A.save()が実行される。この方式は単純なケースではうまく行くが、より複雑なケースでは問題が発生してくる。次のような、ダイアモンド継承下でのメソッド検索の問題について見てみよう。passここでは、クラスDはBとCを継承しており、その両方ともがクラスAを継承している。Aという順序でメソッドを探索していくため、x.save()は、A.save()を参照する。この場合は、あなたが期待するのと違っているはずだ。BとCの両方がAを継承しており、再定義されてクラスAよりも詳細化されたC.save()を、クラスAのメソッドよりも呼びたいと考える人もいるだろう。実際はA.save()が常に呼ばれる。save()メソッドがオブジェクトの状態の保存に使用されるのであれば、クラスCによって定義されたメソッドが呼ばれないことで、Cに関する状態が無視されてプログラムの動作に問題が生じる。この種の多重継承は、既存のコードの中でもめったに使われていないが、新スタイルクラスを使うとこの問題が日常茶飯事となる。新スタイルクラスはベースクラスであるobjectを継承することで行われる。新スタイルクラスでは、どのように多重継承を行っても、必ず上記のようなダイアモンド継承状態になる。passそれに加えて、派生クラスで拡張されるような(__setattr__()など)、いくつかのメソッドをobjectクラスが持っているため、このメソッドの解決順序の重要性は以前よりも高くなった。上記のコードの場合には、C.__setattr__は、クラスDのインスタンスにも適用されるべきである。2.2では、新スタイルクラスの導入のためにメソッド解決順序の見直しを行い、クラス定義時にあらかじめ順序を計算しておき、クラスオブジェクトごとに属性として格納するという方式のMROを適用した。以前の公式ドキュメントでは、深さ優先探索のMROを利用していると書かれていた。この検索順序の中で同じクラスが重複すると、MROのリストの中で後半に出てきた方が削除されるようになった。Aとは違う結果になる。実際には、MROの計算はこれよりもはるかに複雑である。私は、この新しいMROアルゴリズムがうまく働かないようなケースをいくつか発見した。Y)のMROのリストの中でそれぞれ異なる順序で並ぶという特殊なケースがある。これらのクラスは元のクラスをそれぞれ異なる順序で継承しており、さらにそれを他のクラス(Z)が継承している。objectという順序に並ぶ。objectというのは、共通のベースクラスである。私はBとAが逆になっているというのが気に入らなかった。objectという順番にする。このアルゴリズムは最初に検索したときに見つかった順番をなるべく保存しようとする。クラスZの場合には、継承リストの順番が先ということで、ベースクラスXが先に検索される。XはAとBを継承しているため、MROアルゴリズムはこの順番を保存しようとする。2.2で実装されたもので、初期のアルゴリズムとしてドキュメント化した。Pedroni氏は、このドキュメント化されたMROアルゴリズムと、実際にコードを動かした結果が異なるということを発見した。これらの矛盾は上記のような特別な場合以外でも発生していた。説明されている、C3線形化アルゴリズムを採用することが決定された。2.2のMROアルゴリズムの本質的な問題は、探索順を線形に並べるという問題に関係していた。継承階層が複雑になると、それぞれの継承の関係は、シンプルなルールでクラスがどのような順番に並ぶかのチェックが行われる。明らかに、クラスAがクラスBを継承している場合にはMROはAがBの前にあることをチェックしなければならない。同様に、クラスBがクラスCとDを多重継承している場合には、BはCの前にあり、CはDの前にあることがチェックされなければならない。複雑な継承階層の中では、順番を線形化する場合に、これらのルールをすべて満足するようにしたいと思うだろう。クラスAがクラスBの前になければならないということが決められた場合に、クラスBの方がクラスAの前に来るという矛盾した状態にはなって欲しくはない。その場合には結果が未定義なので、そのような継承階層はリジェクトされなければならない。オリジナルのMROが間違っていて、C3アルゴリズムがうまくいくのはこのような場面である。基本的に、C3の背後にあるアイディアは、複雑なクラス継承を行ったときにも、継承の関係から作られた順番のルールをすべてリストアップした場合に、すべてのクラス間でこれらのルールを満足するようなクラスの順番に、一列に並べることができるというものである。順番が一意に決まらない場合には、このアルゴリズムはその継承をエラーとして失敗させるようになっている。2.2で自前作ったMROアルゴリズムを捨て、アカデミックな目でチェックされたC3アルゴリズムを選択することにした。これにより、矛盾した順序を持つ継承を行うと、Pythonがそれをエラーとするようになった。先ほどの例で言えば、クラスXとクラスYの間では、順序に矛盾がある。クラスXを見るとクラスBの前にクラスAがなければならないということになるが、クラスYを見ると、クラスAの前にクラスBがなければならないことになっている。個別に見れば、これらの矛盾は問題ないが、これらのクラスXとYが、同じ継承階層の中に現れる(例えば、ここではクラスZが定義されることによってこれが発生する)と、C3アルゴリズムはこのクラスをリジェクトする。Pythonの「問題があるときには、こっそりと処理してはならない」というルールにもマッチしている。