コーディングガイド

Contents

このドキュメントは、コーディングの要件と PyPy のコードベースで作業をする上での取り決めを説明します。 このドキュメントを注意深く読み、どんな疑問が合った場合でも読み返して下さい。 ドキュメントはコーディングスタイルの問題について多くは語りません。 おおよその場合 PEP 8 にあわせて下さい。 疑わしい場合は、既に存在するコードベースのスタイルに合わせてください。

概要と動機

私たちは Python インタプリタを Python で書いています。 Python を使用することは、アルゴリズム問題ではなく、言語そのものに言及できることはよく知られています。 ぱっと見、これは何の意味もないと思うかもしれませんが、インタプリタの仕組みを理解するためにはよいことです。 これだけで価値があります。しかし、私たちにはより大きな目標があります。

CPython 対 PyPy

CPython の実装と比較すると、 Python は C コードの役割を持ちます。 我々は CPython インタプリタを Python それ自身で書きなおしています。 我々は C レベルでもより柔軟なインタプリタ目指せますが、インタプリタの代替として高級言語による実装を与えるために Python を使います。

明確な利点は、このような高級言語による実装は短く、より単純で読みやすく、多くの実装の詳細がないことです。 このアプローチの欠点は、このインタプリタがとても遅く、 CPython のトップレベルが実行されるまでに時間がかかってしまうことです。

便利なインタプリタを再度手に入れるために、Python による高級な実装から低級なものに変換する必要があります。 もうひとつの直線コースは、 PyPy インタプリタのソースコード全体の解析と、 C のソースコードを再度作ることです。 他の道も様々ありますが、このやや標準的なアプローチにこだわってしてみましょう。

アプリケーションレベルとインタプリタレベルの実行とオブジェクト

Python が我々の全てのコードベースの実装に使われて以降、 インタプリタレベル オブジェクトと アプリケーションレベルオブジェクト の間には注意するべき重要な区別があります。 通常の Python プログラムで書く上であなたが扱う文字です。 インタプリタレベルのコードは、アプリケーションレベルのオブジェクトのオペレーションを呼び出したり、また、属性にアクセスすることはできません。 あなたはPyPy のどんなインタプリタレベルのコードでもすぐにわかるでしょう、 半分の変数とオブジェクト名は w_ という文字列で始まっているからです。 これは、変数やオブジェクトが ラップされた アプリケーションレベルのオブジェクトであることを示します。

ここでは単純な例で違いを示します。 二つの変数 ab の中身を足す、という処理はアプリケーションレベルでは単に a+b と書かれます。 対照的に、インタプリタレベルのコードは space.add(w_a, w_b)` となります。 space はオブジェクトスペースのインスタンス、 w_aw_b は二つの変数のラップされたバージョンの典型的な名前です。

これは、CPython が同じ問題にどうやって対処しているかを思い出すことを救けてくれます。 CPython のインタプリタレベルのコードは C で書かれでいます。従って、典型的な加算処理のコードは PyNumber_Add(p_a, p_b) です。ここでは p_ap_bPyObject* 型の C の変数です。 これは、概念的には我々が Python でインタプリタレベルのコードを各方法に似ています。

また、 PyPy ではインタプリタレベルとアプリケーションレベルの例外を明確に区別します。 アプリケーションの例外は常に OperationError クラスのインスタンスの中に格納されます。 これは、インタプリタで実行している Python のアプリケーション上で表示されるエラーから、インタプリタレベルのコードが失敗したのか(もしくはバグなのか)の区別を容易にします。

アプリケーションレベルのコードはたまに理想的

アプリケーションレベルのコードは、とても高級なので書いたりデバッグすることが簡単になっています。 例えば、辞書オブジェクトの update メソッドを実装しようとしているとします。 アプリケーションレベルでのプログラミングは、 update メソッドの 実行可能な定義 のように見えるようなとてもシンプルな実装を書けます。 例えば以下のようなものです:

def update(self, other):
    for k in other.keys():
        self[k] = other[k]

もし、インタプリタレベルのコードのみを書くことになったら、多くの低レベルな何かのコードを書き、考慮する必要があります。 以下で述べるようにです。:

def update(space, w_self, w_other):
    w_keys = space.call_method(w_other, 'keys')
    w_iter = space.iter(w_keys)
    while True:
        try:
            w_key = space.next(w_iter)
        except OperationError, e:
            if not e.match(space, space.w_StopIteration):
                raise       # re-raise other app-level exceptions
            break
        w_value = space.getitem(w_other, w_key)
        space.setitem(w_self, w_key, w_value)

このインタプリタレベルの実装は、 C のソースコードのそれに似ています。 ただし、メモリマネジメントや Python の例外機構を使えるため、 C で書いたそれよりもまだ読みやすいでしょう。

いかなるケースでも、アプリケーションレベルの実装の方がインタプリタレベルのそれよりも確実に読みやすく、エレガントで、メンテナンス性が高い事を明らかにする必要があります(そして実際に dict.update は PyPy においては本当のアプリケーションレベルで実装されています)。

実際、 PyPy のほぼすべての部分において、アプリケーションレベルのコードがインタプリタレベルのコードの中に使われています。 いくつかのブートストラップ問題を置いておいて、 (アプリケーションレベルの機能は使えるようになる前に、オブジェクトスペースが特定の初期化レベルに達することが必要です)、アプリケーションレベルのコートは通常、望ましいものです。 特定の関数の実装がアプリケーションレベルかインタプリタレベルかを知らないまま関数の呼び出しを許可する (“ゲートウェイ”と呼ばれる) 抽象化機能を持っています。

ランタイムインタプリタは “RPython”

インタプリタレベルの全てのコードを実行可能な C コードを生成するジェネレータを作るには Python のサブセットとしてそれ自身を制限する必要があります。 そして、いくつかのルールに準拠して、実行可能な低レベル言語に変換します。 アプリケーションレベルのコードは、まだ Python の全ての表現を使えます。

ソースからソースへの変換 (Starkiller もしくは最近では ShedSkin のような例があります) とは異なり、生きた Python コードオブジェクトから変換を開始し、 Python インタプリタを構成します。 我々の Python 実装は、バイトコート解釈作業の実行時に “RPythonic” として参照されることがあるような静的な方法で動作する必要があります

ただし、PyPy インタプリタが Python プログラムとして開始したとき、 Python 言語のすべてを使えるような特定の時点でのポイントに達するまでのすべての実行は静的である必要があります。 プログラムの初期化中は、動的なコード生成を含む Python の動的な処理が自由に使えます。

現行の実装に含まれるサンプルコードは非常にエレガントです。 Python インタプリタに含まれるすべてのオペコードの定義に使われる dis モジュールは、バイトコードインタプリタからインポートされ、使われています。 (pypy/interpreter/pyopcode.py にある __initclass__ を見てください) このおかげで余分なモジュールを PyPy に追加することを抑えられています。 インポートしたコードはスタートアップ時に走り、 CPython ビルトインインポート関数を使うことを許可しています。

スタートアップコードの終了後は、すべての結果のオブジェクト、関数、コードブロックなどは 下記にあるような特定のランタイムの制限に従う必要があります。 そのような制限がある理由とその背景について説明します。 変換とプログラム全体の解析 (“型インタフェイス”) の実行中は、 RPython で定義された制限を利用します。 これは、純粋な整数型のオブジェクトやインスタンスのための効率的なマシンレベルコードに置き換えるコードジェネレータを有効にします。

RPython

RPython の定義

RPython は静的解析が可能なように制限された Python のサブセットです。 言語への追加があり、いくつかの要素が想定外の動作をする可能性があります。 これらは、その考慮するべき制限の大雑把なリストです。 注意としては、あなたが遭遇する制限には多くの特殊なケースがあることが挙げられます。 正確な定義としては、「RPython は我々の翻訳toolchainが受け入れられるものが全て」です :)

制御フローの制限

変数

変数は オブジェクトの制限 によってそれぞれのコントロールフローポイントで解釈された一つの型の値を含む必要があります。 それが意味するところは、例えば、コントロールパスの接続時に、同じ変数に対して int と string の両方が使われることが回避されなければならないということです。 その他の多くの型と一緒に None を含めることは許可されています。(基本的には None は NULL ポインタとして使われます) 多くの型とはすなわち wrapped objects, クラスのインスタンス、リスト、辞書、文字列などです 、int, float, tuple は含まれません。

定数

全てのモジュールグローバル変数は定数として扱われます。その束縛を実行時に変更することはできません。 また、グローバルな (すなわち実行前に作られた) リストと辞書は不変なオブジェクトであるとして扱われます。 もし、グローバルなリストが変更された場合、一貫性のない結果をもたらすでしょう。 ただし、グローバルインスタンスはこの制限を持ちません。なので、もし変更可能なグローバルな状態を扱いたいのであれば、シングルトンインスタンスの属性としてストアしてあげればよいのです。

制御構造

全て使えますが、 for ループはビルトインの型のみに制限され、ジェネレータはかなりの制限がかかっています。

range

rangexrange は同一です。 range は、その結果が変更される場合を除いて必ずしも配列を作るわけではありません。 これは、どこでも使え、完全に実装されています。 CPython からの違いとして、唯一確認可能ものは、 xrange の start, stop, step フィールドにアクセスできないことです。

定義

実行時にクラスや関数を定義することはできません。

ジェネレータ

ジェネレータはサポートされますが、実際のスコープはとても制限されています。 別々のジェネレータを一つの制御点において混ぜて使用することはできません。

例外

  • 完全にサポートしています
  • 下記 例外のルール で組み込み演算で送出される例外に関する制限を説明しています

オブジェクトの制限

以下の通りです

整数, 浮動少数, 真偽値

使えます。

文字列

大体使えます。しかし、文字列のメソッド全てをサポートしているわけではないのと、必ずしも全ての引数を受け付けるとは限りません。 負値によるインデックスアクセスが可能です。もし、インデックスアクセスの値が非負であることをトランスレータが検出した場合は、わずかに効率的なコードを出力します。 文字列に対するスライス処理を行う場合は、開始と終了のインデックスが非負である必要があります。 文字列とユニコード文字列の暗黙の変換が行われることはありません。

タプル

タプル変数の長さはありません。それらは ストアもしくはペアの返却, n要素のタプルの値として使われます。 型の組み合わせごとに別々の要素と長さを構成し、型を混ぜることはできません。

リスト

リストは動的にメモリ領域を確保される配列として使われます。 リストは多めに確保されるため、 list.append() はそれなりに高速に動作します。 ただし、もしあなたが固定長のリストを使う場合は、コードはより効率的になります。 アノテータはほとんどの場合においてリストが固定長であるかどうかを把握できます。リスト内包表記を使った時であってもです。 負値か境界外のインデックスは、以下のほとんどのオペレーションで使えます。

  • インデックスアクセス: 正負両方のインデックスによるアクセスができます。インデックスはチェックされ、場合によっては IndexError 例外を送出します。
  • スライス: スライスの開始インデックスはリストの範囲内になければいけません。終了インデックスは必要ありませんが、開始インデックスよりも小さい値ではいけません。 全ての負のインデックスは使えません、ただし、 [::-1] のような場合は例外的に使えます。
  • その他の演算子: +, +=, in, *, *=, ==, != は予想のとおりに動きます。
  • メソッド: append, index, insert, extend, reverse, pop について。 pop() の呼び出しでインデックスを渡した場合は、 インデックスアクセス と同様のルールが適用されます。 insert() の呼び出しでインデックスを渡す場合は、境界内かつ非負の値でなければいけません。

辞書

辞書のキーはハッシュ化可能な一つの型しか使えません。独自のハッシュ関数や、独自の比較関数は使えません。 pypy.rlib.objectmodel.r_dict は独自のハッシュ関数を利用するために使えます。

リスト内包表記

割り当て済み、初期化済みの配列に使うことがあります。

関数

  • 静的に呼び出される関数は、デフォルト引数と可変長の引数を使うことがあります。 (タプルの代わりにリストが渡されることがあり、タプルに依存しないコードが書きだされます)
  • 動的ディスパッチは全ての呼び出される関数か、少なくとも「十分な互換性」を持つ関数と等しいシグネチャを使うことを強制します。 この懸念は主にメソッドがオーバライドされていたり、任意の方法で別のクラスの別の定義が与えられたときのメソッド呼び出しのものです。 関数オブジェクトを明示的に操作するという少ない共通のケースもまた懸念されます。 正確な互換性のルールはむしろ関係を記述しています。 (もしそれらが破れた場合、あなたは RTyper からの明示的なエラーを得、曖昧ではないクラッシュをするでしょう。)

組み込み関数

いくつかの組み込み関数は使えます。正確には pypy/annotation/builtin.py に定義されているものがそうです。 (def builtin_xxx() という定義を見てください) いくつかの組み込み関数はサポートしている範囲を制限されることがあります。

int, float, str, ord, chr... これらは単純な変換関数として使えます。 int, float, str... これらは isinstance() で使った時のみ内部型として特別な意味を持つことに注意してください。

クラス

  • メソッドとその他のクラスの属性は実行後に変更できません
  • 単一継承は完全にサポートしています
  • 単純な mixin は機能しますが、 mixin される側のクラスには _mixin_ = True というクラス属性を付けなければいけません
  • クラスはもちろんファーストクラスオブジェクトです

オブジェクト

通常のルールが適用されます。 __init____del__ 以外の特殊メソッドは使えません。

以上のように、いくつかの型に関して、かなりの制限があることを気をつける必要があります。

整数型

整数型を実装する中で、現在の CPython の実装では、整数型はかなり流動的である、という問題に当たりました。 Python 2.4 以降では、整数型はオーバーフローが起こると長正数型に変異します。 対照的に、デフォルトでは、必要があると分かっている場合には、オーバーフローをチェックしている間にマシンのビットサイズに収まる範囲でのでの算術演算を実行する方法が必要です。 また、変換の前後で一貫性のある振る舞いが必要です。

通常、整数は符号付きの算術演算を使います。 それが意味するところは、変換前はオーバーフローが発生するケースで長整数型になり、変換後はラップアラウンドしないということです。 我々はいつでも制御が必要で、次のヘルパを使用します (pypy/rlib/rarithmetic.py ここに存在します):

ovfcheck()

この特殊関数は、単一の算術演算を引数として使われる必要があります。 z = ovfcheck(x+y) のように。 その目的は、引数で与えられた操作をオーバーフローチェックモードで実行するためにあります。

Python の実行時、 ovfcheck() 関数は自身の結果のチェックをして、もし結果が long であれば OverflowError を送出します。 しかし、コード生成器は ovfcheck() 関数をヒントとして使います。 コード生成器は、全ての ovfcheck(x+y) 式を C でのオーバーフロー検出の追加に置き換えます。

intmask()

この関数はラップアラウンドする算術演算に使います。 この関数は引数の下位ビット以外をマスクして取り除き、返しますが、 C での “signed long int” にフィットするわけではありません。 その目的は、 Python を処理する上で、直前の演算結果の Python での long から Python の int に戻すための変換です。 コード生成器は intmask() を完全に無視し、とにかくラップアラウンドする符号付き演算を行うようにします。 (C での “int” と “long int” 相当の区別を持たず、現時点では “long ints” を前提としています。)

r_uint

いくつかのケース (例えばハッシュテーブルの操作など) でマシンサイズ依存の符号なし演算が必要な場面があります。 それらのケースのために r_uint クラス があります。勝手にラップアラウンドする word サイズの符号なし整数 の Pure Python 実装です。 このクラスの目的は (ヘルパ関数としての上記に反して) 一貫性のある型付けのためにあります。 Python とアノテータのどちらもプログラムの中で r_uint インスタンスを伝達し、全ての演算でそれらを符号なし整数として解釈するでしょう。 演算の中で(符号付き)整数と r_uint 混ぜると、結果は r_uint になり、結果が符号なし整数であることを意味します。 r_uint から通常の符号付き整数に戻すには intmask() を使います。

例外のルール

例外はデフォルトでは単純なケースには生成されません:

#!/usr/bin/python

    lst = [1,2,3,4,5]
    item = lst[i]    # このコードは範囲外アクセスをチェックしていない

    try:
        item = lst[i]
    except IndexError:
        # 文句を言われた

例外ハンドラのないコードは例外を送出しません (翻訳された後は。 CPython のトップレベルで実行する場合は、もちろん例外を送出する可能性があります) 例外ハンドラを提供することでエラーチェックをします。 エラーチェックなしには操作を失敗しないシステムを保証しません。 このルールは 関数呼び出し には適用されません。 呼ばれる関数はあらゆる例外を送出すると仮定されます。

例:

x = 5.1
x = x + 1.2       # 浮動小数点のオーバーフローはチェックされません
try:
    x = x + 1.2
except OverflowError:
    # float result too big

ただし:

z = some_function(x, y)    # どんな例外でも送出できます
try:
    z = some_other_function(x, y)
except IndexError:
    # some_other_function() の中で明示的に送出された IndexError のみキャッチします
    # 他の例外は送出でき、ここではキャッチされないでしょう。

ovfcheck() 関数は次のように上を述べたものと同様のルールです: オーバーフローが発生するケースでは、 明示的に OverflowError を送出し、どこでもキャッチできます。

明示的に送出されたか、もしくは再送出された例外は常に生成されます。

PyPy は CPython のトップレベルでデバッグできます

PyPy は標準的な CPython で実行できるという利点があります。 それが意味することは、PyPy を全ての例外ハンドラを有効にして実行できるということです。 そのため、暗黙のアサーションに準拠してキャッチする可能性があります。

ラッピングルール

ラッピング

PyPy は 2つのレベルの Python ソースコードからできています。 片方は アプリケーションレベルのコード です。 これは Python コードからは期待通りの機能を果たすようものをいくつか実装していて、通常の Python コードのように見えます(例: ビルトイン関数の zip() と同じものの Pure Python の実装)。 もうひとつは インタプリタレベルのコード です。 これはインタプリタのデータとオブジェクトを直接操作する必要があるような機能です(例: インタプリタのメインループや様々なオブジェクトスペース)。

アプリケーションレベルのコードはオブジェクトスペースを明示的に参照することはありません。 オブジェクト操作のサポートのためにオブジェクトスペースを使って実行しますが、これは暗黙的に行われます。 アプリケーションレベルのコードでは特定の規則のための必要はありません。 続編についてはインタプリタレベルのコードのみです。(理想的には、混乱を避けるためにアプリケーションレベルのコードが spacew_xxx を呼び出す必要がないようにするべきです)

上記で示した例では w_ というプレフィクスはとてもふんだんに使われています。 アプリケーションオブジェクトに対応したインタプリタレベルオブジェクトの実装のためにオブジェクトスペースを構築します。 そして、その実装は PyPy のコーディング規則によって、 ラップされた (もしくは ボックス化された) オブジェクトを使うことで対処しています。 オブジェクトスペースごとに wrap, unwrap, int_w, interpclass_w などの単純なビルトイン型オブジェクトが二つのレベルの間を行き来するためのオペレーションを提供しています。 オブジェクトスペースごとに、いくつかの内部構造を持つ適切なインタプリタレベルのクラスなどの他の Python の型も実装しています。

例えば、アプリケーションレベルの Python の list標準オブジェクトスペース では wrappeditems という属性を持った W_ListObject のインスタンス (ラップされたオブジェクトとしてアプリケーションレベルのリストのアイテムを含むインタプリタレベルのリスト) として実装されています。

このルールの詳細は下記で説明します。

名前付の規則

  • スペース: オブジェクトスペースはインタプリタレベルのコードにおいてのみ見えます。これは space という名前にる受け渡しが行われることになっています。
  • w_xxx: アプリケーションレベルコードから見えるどんなオブジェクトであっても、オブジェクトスペースによって明示的に管理されます。 インタプリタレベルの観点から、これは ラップされた オブジェクトと呼ばれます。 w_ のプリフィクスはどんなタイプのアプリケーションレベルのオブジェクトに対しても使われます。
  • xxx_w: ラップされたオブジェクトのためのインタプリタレベルのコンテナです。list や dict などがラップされたオブジェクトの例です。list や dict とラップされたオブジェクトを混同しないようにしてください。それらは通常のラップされたオブジェクトで、 w_ のプリフィクスが使われています。

w_xxx の操作

コアのバイトコードインタプリタはラップされたオブジェクトをブラックボックスとして扱います。 ブラックボックスの中を直接検査することは許されていません。許可された操作は全てオブジェクトスペースに実装されています。 それらは space.xxx()` と呼ばれます。 xxx は標準的な操作の名前です( add, getattr, call, eq ...)。 オペレーションは オブジェクトスペースのドキュメント に記載されています。

don’t do w_x == w_y or w_x is w_y という短い警告がでます! 上記ルールのが存在するため、たとえアプリケーションレベルで同じオブジェクトを含むとしても、二つのラップされたオブジェクトが関連している理由にはなりません。 同値性をチェックするには、 space.is_true(space.eq(w_x, w_y)) もしくはよりよいショートカットである space.eq_w(w_x, w_y) が返すインタプリタレベルのブール値を使います。 同一性をチェックするには space.is_true(space.is_(w_x, w_y)) もしくはより短い space.is_w(w_x, w_y) を使います。

アプリケーションレベルの例外

インタプリタレベルのコードは自由に例外を使えます。 ただし、全てのアプリケーションレベルの例外はインタプリタレベルでは OperationError として扱われます。 別の言い方をすると、アプリケーショレベルで発生する可能性のある全ての例外は、内部的には OperationError です。 オブジェクトスペースの操作によって報告される全てのエラーがこのように扱われます。

アプリケーションレベルの例外を発生させる:

raise OperationError(space.w_XxxError, space.wrap("message"))

特定のアプリケーションレベルの例外をキャッチする:

try:
    ...
except OperationError, e:
    if not e.match(space, space.w_XxxError):
        raise
    ...

これは、全てのアプリケーションレベルの例外をキャッチするようにしています。 そして、再度特定の処理したい例外とマッチするかどうかをチェックし w_XxxError 、そうでなければ再度例外を送出しなければいけません。 例外のインスタンス ee.w_typee.w_value という名前でアクセスできる二つの属性を持ちます。 e.w_type を例外のマッチに使わないでください。この方法だと、処理対象の例外のサブクラスのインスタンスが送出された場合にマッチに失敗してしまいます。

PyPy におけるモジュール

アプリケーションプログラムから見えるモジュールは、インタプリタからインポートされたものかアプリケーションレベルのファイルです。 PyPy は CPython 標準ライブラリの殆ど全てのモジュールを再利用します。現在はバージョン 2.7.1 から使っています。 時々 変更したモジュール と - 多くの - 回帰テストが必要になります。それらは CPython の実装の詳細に依存しているためです。

オリジナルの CPython モジュールの変更にとどまらず、フルスクラッチで書き直した場合は、 lib_pypy/ が実際のアプリケーションレベルのモジュールです。

インタプリタレベルのオブジェクトへのアクセスが必要なときは、モジュールを pypy/module に置いています。 ここにあるモジュールは 混合モジュール機構 を使用しています。 これは、インタプリタレベルとアプリケーションレベルの両方のパーツを使って実装する際に便利です。 純粋なインタプリタレベルのモジュールのための余分な機能はありません。 混合モジュールを書くだけで、アプリケーションレベルの部分はありません。

モジュールを実装する場所を決定する

py.py の実行中に、モジュールがどこから来たものなのかを対話的に見つけられます:

>>>> import sys
>>>> sys.__file__
'/home/hpk/pypy-dist/pypy/module/sys'

>>>> import cPickle
>>>> cPickle.__file__
'/home/hpk/pypy-dist/lib_pypy/cPickle..py'

>>>> import opcode
>>>> opcode.__file__
'/home/hpk/pypy-dist/lib-python/modified-2.7/opcode.py'

>>>> import os
>>>> os.__file__
'/home/hpk/pypy-dist/lib-python/2.7/os.py'
>>>>

モジュールディレクトリ / インポート順

ここでは、PyPy が Python モジュールを探す順番について話します:

pypy/modules

インタプリタ/アプリケーションレベルが混合された sys__builtin__ などの組み込みモジュールです。

PYTHONPATH の中身

PYTHONPATH 環境変数で指定された : で区切ったディレクトリのリストで、それぞれのディレクトリからアプリケーションレベルモジュールを探します。

lib_pypy/

Python のみで再実装されたモジュールがあります。

lib-python/modified-2.7/

CPython から書き換えなければならなかったファイルとテストです。

lib-python/2.7/

変更していない CPython ライブラリです。 ここにあるものはチェックしていません (Never ever check anything in there って合ってる?)

CPython 標準モジュールと回帰テストの変更

PyPy は CPython とても互換性がありますが、時々コピーした標準ライブラリを変更する必要があります。 多くの場合、 PyPy はデフォルトでは全て新スタイルクラスとして動作しますが、いくつかの場所で CPython は旧スタイルクラスの挙動に依存しているために発生します。

もし lib-python/2.7 に含まれるモジュールとテストを変更する場合は、まず lib-python/modified-2.7 ディレクトリにコピーしてください。 Mercurial のコマンドラインでは以下のコマンドで行います:

$ hg cp lib-python/2.7/somemodule.py lib-python/modified-2.7/

その後、 lib-python/modified-2.7/somemodule.py を編集してコミットします。 オリジナルの CPython ツリーをクリーンに保ち、何を変更したかを明確にするために、このコピーの操作は重要です。

インタプリタ/アプリケーションレベルを混合したモジュールの実装

モジュールが PyPy のインタプリタレベルにアクセスする必要がある場合、混合モジュールとして実装します。

混合モジュールは pypy/module にあるディレクトリで、 __init__.py を含まなければいけません。 指定した名前のみ混合モジュールの名前空間としてエクスポートされます。

混合モジュールは、時々いくつかの関数を C (もしくはターゲットの言語)で書くために必要です。 詳細は rffi外部関数のドキュメント を見てください。 後者のアプローチは面倒で、段階的に廃止され、今は少しのエッジのみ残っています。

アプリケーションレベルの定義

アプリケーションレベルの定義は pypy/module にあるディレクトリの中の __init__.py ファイルにある appleveldefs です。 例えば、 pypy/module/__builtin__/__init__.py の中に以下のような __builtin__.locals の指定があります:

...
'locals'        : 'app_inspect.locals',
...

app_ プレフィクスは app_inspect サブモジュールとして解釈され、アプリケーションレベルでラップされた locals 関数はそれに従って抽出されます。

インタプリタレベルの定義

インタプリタレベルの定義は pypy/module にあるディレクトリの中の __init__.py ファイルにある interpleveldefs です。 例えば、 pypy/module/__builtin__/__init__.py の中に以下のような __builtin__.len の定義があります:

...
'len'       : 'operation.len',
...

operation サブモジュールはインタプリタレベルのもので、 len はアプリケーションレベルにエクスポートできると予想されます。これは、 operation.len() の定義です:

def len(space, w_obj):
    "len(object) -> integer\n\nReturn the number of items of a sequence or mapping."
    return space.len(w_obj)

公開されるインタプリタレベル関数は、通常 space といくつかのラップされた値を引数として受け取ります。 (ラッピングのルール 参照)

interpleveldefs 辞書にある便利なショートカットも使えます。 すなわち、括弧の中では(ファイルから直接取ってくる代わりに)直接インタプリタレベルの式を書けます:

...
'None'          : '(space.w_None)',
'False'         : '(space.w_False)',
...

インタプリタレベルの式は実行する際に space を束縛します。

pypy/module にエントリを追加する (例えば mymodule など) と、自動的に py.py と translate.py に新しいコンフィグオブションが作られ (–withmod-mymod と –withoutmod-mymodule のようなものです (デフォルトは後者)) ます。

lib_pypy/ にあるモジュールのテスト

lib_pypy/pypy_test/ ディレクトリに移動して、 lib_pypy ディレクトリツリーに対するテストを走らせるためにテストツールを実行できます。(“py.test” か “python ../../pypy/test_all.py” などです) 註 lib_pypy/pypy_test/ はそれらのテストをインタプリタレベルで走らせることが許可され、推奨されていますが、 lib_pypy/ のモジュール群は最終的に PyPy のアプリケーションレベルで動きます。 これにより、CPython に比べて私たちの Python コードでの再実装の素早いテストが可能になります。

pypy/module にあるモジュールのテスト

pypy/module とそのサブディレクトリに変更を加えた場合は、単純に いつものテストを実行します

lib-python にあるモジュールのテスト

CPython の回帰テストを PyPy に対して実行するために、 lib-python/ ディレクトリに切り替え、互換性テストのためのテストツールを実行します。(XXX テストレポート生成のための Windows の互換性の検査)

命名規則とディレクトリ構成

ディレクトリとファイルの命名

  • ディレクトリ/モジュール/名前空間はすべて 小文字
  • ディレクトリとファイルの名前に複数形は使わない
  • __init__.py は通常空だが、 pypy/objspace/\*pypy/module/\*/__init__.py は例外
  • 四階層以上のディレクトリ構成を作らない
  • ファイル名簡潔で容易にわかるようにする

Python オブジェクトの命名

  • クラス名は CamelCase (キャメルケース/ラクダ記法)
  • 関数名/メソッド名は小文字を _ で区切ったもの (snake case/スネークケース)
  • オブジェクトスペースのクラスは XyzObjSpace のように綴る。 例:
    • StdObjSpace
    • FlowObjSpace
  • インタプリタレベルと ObjSpace に含まれる全てのボックス化された値は、”ラップされた値” を示す w_ で始まる これには w_self も含まれる。 w_ をアプリケーションレベルの Python でのみ使われるコードに使ってはいけない。

リポジトリのコミットとブランチ

  • 何人かは diff を読んでいるので分かりやすいコミットログメッセージを書くこと。
  • 以前は trunk と呼ばれていたものは Mercurial では default ブランチと呼ばれる。 Mercurial のブランチはいつもリポジトリの他の部分と一緒に push される (try1 というブランチが存在しないと仮定すると) try1 というブランチを作るには以下のようにする:

    hg branch try1

    コミットするとブランチはリポジトリに保存される。 default ブランチに戻るには以下の操作を行う:

    hg update default

    さらに詳細を知りたい場合は help を使うか オフィシャル wiki を参照する

開発のバグ/機能要望トラッカーを使う

Richard Jones の roundup というアプリケーションをベースにした 開発トラッカー を運用しています。 バグや機能要望を登録したり、次のマイルストーンのためにになにが行われているかを電子メールと Web インタフェイスから参照できます。

codespeak へのログインと登録

codespeak のアカウントを持っていれば、トラッカーへのログインに使用できます。 もし持っていなければ簡単に トラッカーを使って登録 できます。

PyPy のテスト

テストは、定型句なしに unittest が書ける py.test ツールをベースにしています。 ディレクトリに含まれる全てのモジュールのテストは、通常 test というサブディレクトリに存在します。 そこには基本的な二つのタイプの単体テストがあります。

  • インタプリタレベルのテスト: PyPy のインタプリタと同じレベルで実行されます。
  • アプリケーションレベルのテスト: アプリケーションレベルで実行されます。ただの Python コードのように見えますが、 PyPy によって解釈・実行されます。

インタプリタレベルのテスト

テスト関数は以下のように書きます:

def test_something(space):
    # space を使ったテスト ...

class TestSomething(object):
    def test_some(self):
        # self.space を使ったテスト

テストに使う関数の名前の test プレフィクスと、テストに使うクラスの名前に Test が含まれていることは必須です。 どちらのケースでも Python モジュールはモジュールグローバルレベルでインポートでき、普通の ‘assert’ 文は py.test ツールのおかげで使えます。

アプリケーションレベルのテスト

PyPy の適合性と振る舞いの適切さのテストのためには、特定のコーディングスタイルや制限に注意する必要のない “普通の” アプリケーションレベルの Python コードを書けば、多くの場合は十分です。 アプリケーションレベルのテストを選択するなら、通常は以下のようなものになります:

def app_test_something():
    # アプリケーションレベルのテストコード

class AppTestSomething(object):
    def test_this(self):
        # アプリケーションレベルのテストコード

それらのアプリケーションレベルのテスト関数は、 PyPy のトップレベルで実行されます。 すなわち、インタプリタレベルの詳細にアクセスできないということです。 グローバルレベルでインポートしたモジュールは使えません。 それらのモジュールはインタプリタレベルでインポートされたもので、テストコードはアプリケーションレベルで実行されるためです。 モジュールを使う場合は、テスト関数の中でインポートしてください。

AppTest の中にデータを受け渡す別の可能性は、 AppTest クラスの setup_class メソッドを使うことです。 w_ で始まる全てのラップされたオブジェクトは、self を介して (w_ の前置なしで) 実際のテストメソッドの中からアクセスできます。 例:

class AppTestErrno(object):
    def setup_class(cls):
        cls.w_d = cls.space.wrap({"a": 1, "b", 2})

    def test_dict(self):
        assert self.d["a"] == 1
        assert self.d["b"] == 2

コマンドラインツール: test_all

PyPy のほとんどすべてのテストを呼び出し、実行できます:

python test_all.py file_or_directory

which is a synonym for the general py.test utility located in the py/bin/ directory. For switches to modify test execution pass the -h option.

これは py/bin/ ディレクトリにある py.test ユーティリティの別名です。 -h オプションを渡すことでテストの実行を切り替えます。

カバレッジのレポート

カバレッジポートを取得するために、 pytest-cov プラグインが含まれています。 いくつかの追加モジュール (coveragecov-core) と、 カバレッジテストを一度にインストールするには以下のコマンドを実行します:

python test_all.py --cov file_or_direcory_to_cover file_or_directory

規則のテスト

  • 機能を追加するためには、適切なテストを追加する必要があります。 (失敗できるように最初にテストを書くことは、多くの場合理に適っています。)
  • 全ての PyPy のソースコードのには、単体テストが含まれる test/ ディレクトリがあります。 このようなスクリプトは通常直接実行できるか pypy/test_all.py の実行によって実行されます。

ドキュメントとウェブサイトの変更

ドキュメント/ウェブサイトのファイルはローカルリポジトリに含まれます

ほとんどの PyPy のドキュメントは pypy/doc にあります。 単純に ReST でマークアップされた ‘.rst’ ファイルを編集したり追加したりできます。 ここは ReST クイックスタート ですが、単に既に存在するドキュメントを見て、どのように動いているかを見られます。

http://pypy.org/ のウェブサイトは個別に管理されています。 今は https://bitbucket.org/pypy/pypy.org のリポジトリに存在します。

ドキュメント/ウェブサイトの変更の自動テスト

自動で参照の整合性と ReST の適合性のテストをしています。 テストの実行のためには sphinx のインストールが必要です。 インストール後、ローカルにチェックアウトしたリポジトリのドキュメントディレクトリにに移動し、 Makefile を走らせます:

cd pypy/doc
make html

変更した後にエラーメッセージが出ていない場合、少なくとも ReST のエラーなないことと無効なローカル参照がないという可能性が高いです。 ドキュメントディレクトリに .html ファイルがありブラウザで開けます。

さらに、 ドキュメント中の外部リファレンスをチェックしたいのであれば:

make linkcheck

とすることで外部の URL が生きているかのチェックができます。

Project Versions

Table Of Contents

Previous topic

Goals and Architecture Overview

Next topic

PyPyとCPythonの違い

This Page