PyPyとCPythonの違い

このページはPyPy PythonインタープリターとCPythonとのわずかな違いと非互換性について説明します。 これらの違いのいくつかは「デザイン」であり、CPythonでのバグっぽい挙動に対して、 バグをコピーしたくないと思うからです。

ここに記載されていない違いは、PyPyのバグだと考えられます。

拡張モジュール

サポートしている拡張モジュールのリスト:

  • 組み込みモジュールとしてサポート ( pypy/module/):

    __builtin__ __pypy__ _ast _bisect _codecs _collections _continuation _ffi _hashlib _io _locale _lsprof _md5 _minimal_curses _multiprocessing _random _rawffi _sha _socket _sre _ssl _warnings _weakref _winreg array binascii bz2 cStringIO clr cmath cpyext crypt errno exceptions fcntl gc imp itertools marshal math mmap operator oracle parser posix pyexpat select signal struct symbol sys termios thread time token unicodedata zipimport zlib

  • Supported by being rewritten in pure Python (possibly using ctypes): see the lib_pypy/ directory. Examples of modules that we support this way: ctypes, cPickle, cmath, dbm, datetime... Note that some modules are both in there and in the list above; by default, the built-in module is used (but can be disabled at translation time).
  • (できる限り ctypes を使って)ピュアPythonで書き直してサポート: lib_pypy/ ディレクトリを参照。 この方法を使ってサポートしたモジュールの例: ctypes, cPickle, cmath, dbm, datetime いくつかのモジュールではそのモジュール内と上記のリストの両方で、標準により、 組み込みモジュールが使われる(、しかし解釈時には無効になる)ことに注意しなければなりません。

上で言及されていない、 lib_pypy/ にもない拡張モジュール( 標準CPythonでのCで書かれたモジュール)はPyPyでは利用できません。 (cpyext を用いることで利用できるかもしれません。)

GC(ガベージコレクション)戦略に関連する違い

PyPyで使われ、実行されているGCの多くは参照カウントに基づいていませんので、到達不可能になってもオブジェクトは即座に解放されません。 この影響により、スコープから出ても、ファイルが即座に閉じられません。 書き出し用に開いたファイルに対して、データをしばらく出力バッファに置いたままにすることができ、ディスク上のファイルを空、または、不完全に見えさせます。

このことはGCで参照カウントアプローチを使わずに本質的に解決することは不可能です。 CPythonでの実装結果は明確に言語設計で決定したものではなく、実装の副作用として記述されています: これを当てにするプログラムは、基本的にいんちきです。 それにJythonかIronPython(または、PyPy自身のような、Java、または、.NETのいかなるPythonのポート)で採用する機会が全くないなら、 言語仕様に基づいてCPythonの振舞いを実装しようとするのは、正気の沙汰ではありません。

これは __del__ メソッドが呼ばれる正確な時間に影響し、PyPy(Jython, IronPythonでも)では信頼できません。 また、弱参照が予想より少し長く生き残るかもしれないことを意味します。 これは(weakref.proxy()が返すような)「弱いプロキシ」となり、あまり役にたちません。 PyPy上でほんの少しだけ長く生き残り、突然本当に死亡し、次のアクセスで ReferenceError を挙げます。 弱プロキシを使うすべてのコードのあらゆる場所で ReferenceError を注意深くキャッチしなけばなりません。

副作用として、ジェネレーター内の finally 節はジェネレーターオブジェクトがGCされたときのみ実行されます。( issue 736 参照)

GCの違いによっていくつかの特別な影響があります。 一番顕著なのは、もしオブジェクトが __del__ を持つ場合、PyPyでは __del__ が一度以上呼ばれることはありません。: しかし、CPythonではオブジェクトを復活させ、再度死ぬならば、何回か同じ __del__ を呼びます。 CPythonではお互いを射すオブジェクトでは __del__ メソッドは「正しい」順番で呼ばれますが、CPythonとは異なり、お互いにオブジェクト参照する死のサイクルがあれば、それらの __del__ メソッドはどこでも呼ばれます。 CPythonは代わりにgcモジュールの garbage リストにそれらを入れるでしょう。 詳しい情報はブログを参照して下さい。 [1] [2]

minimark と呼ばれる標準GCを使うことで、ビルトイン関数 id() はCPythonと同様に動作します。 他のGCでは(オブジェクトが何度か移動できるので)実際のアドレスと異なる数字を返し、何度も呼ばれることでパフォーマンス問題を引き起こします。

オブジェクトの長いチェインがあり、それぞれ次のオブジェクトの参照をもち、それぞれ __del__ を持つ場合、 PyPy GCのパフォーマンスがひどく悪くなることに注意してください。 明るい言い方をすると、他のほとんどの場合では、ベンチマークがPyPy GCのパフォーマンスがCPythonよりはるかによくなります。

別の違いは、 __del__ メソッドを既存のクラスに追加すると呼ばれないということです。

>>>> class A(object):
....     pass
....
>>>> A.__del__ = lambda self: None
__main__:1: RuntimeWarning: a __del__ method added to an existing type will not be called

さらによりわかりづらいこと : インスタンスに __del__ メソッドをつける場合、 旧スタイルクラスでは同じことをしてもエラーになりません。(CPythonでは新スタイルクラスの場合でさえも動作しません) PyPyでは実行時に警告が出ます。 この場合、初めからクラスに __del__ メソッドを持つように修正してください。

組み込み型のサブクラス

「組み込み型のサブクラスの適切なオーバーライドメソッドがさりげなく呼ばれたり、 呼ばれなかったりする」場合の公式なCPythonの仕様はありません。 大体、これらのメソッドは同じオブジェクトの他の組み込みメソッドから決して呼ばれません。 例えば、 dict のサブクラスで __getitem__() をオーバーライドしても、組み込みの get() などから呼ばれません。

前述の内容はCPythonでもPyPyでも正しいです。 違いは、組込み関数、または、メソッドが self ではない のオブジェクトのオーバーライドされたメソッドを呼ぶ場合に起こります。 PyPyでは一般的にいつでも呼ばれますが、CPythonではそうではありません。 例えば、PyPyでは dict1.update(dict2)dict2 が一般的にオブジェクトにマッピングされ、 keys()__getitem__() メソッドがオーバーライドされて呼ばれるとみなされます。 そのため、下のコードは PyPy では 42 を出力しますが、 CPython では foo を出力します。:

>>>> class D(dict):
....     def __getitem__(self, key):
....         return 42
....
>>>>
>>>> d1 = {}
>>>> d2 = D(a='foo')
>>>> d1.update(d2)
>>>> print d1['a']
42

Mutating classes of objects which are already used as dictionary keys

Consider the following snippet of code:

class X(object):
    pass

def __evil_eq__(self, other):
    print 'hello world'
    return False

def evil(y):
    d = {x(): 1}
    X.__eq__ = __evil_eq__
    d[y] # might trigger a call to __eq__?

In CPython, __evil_eq__ might be called, although there is no way to write a test which reliably calls it. It happens if y is not x and hash(y) == hash(x), where hash(x) is computed when x is inserted into the dictionary. If by chance the condition is satisfied, then __evil_eq__ is called.

PyPy uses a special strategy to optimize dictionaries whose keys are instances of user-defined classes which do not override the default __hash__, __eq__ and __cmp__: when using this strategy, __eq__ and __cmp__ are never called, but instead the lookup is done by identity, so in the case above it is guaranteed that __eq__ won’t be called.

Note that in all other cases (e.g., if you have a custom __hash__ and __eq__ in y) the behavior is exactly the same as CPython.

警告の無視

めったに発生しない厄介なケースの多くで、CPythonは静かに例外を飲み込み、見えなくすることができます。 こうなるケースはたくさんありますが、ほとんどのケースはめったに起こりません。 もっとも有名なのは、カスタム比較メソッド(例 __eq__)、辞書の検索、 isinstance() などの組み込み関数の呼び出しです。

この挙動は(hasattr() 用の例のように)デザインとドキュメントによって明確になっていませんので、 PyPyでは多くの場合に代わりに例外の伝播を行います。

寄せ集め

  • sys.setrecursionlimit() はPyPyでは(不要であり、)無視されます。 CPythonでは、コールのネスト数の最大値を設定します。 これを超えると実行エラーが起こります。 PyPyで他の実行エラーが原因でスタックがオーバーフローし、制限はより低レベルで チェックされます。 (制限は現在768KBでハードコードされ、Linux上では大体1480 Python callに相当します。)
  • __class__ への割り当てはCPython 2.5で動作する場合に制限されます。 CPython 2.6、2.7ではほんの少し多くの場合でも動作しますが、PyPyではいまのところサポートしていません。 (必要ならばサポート可能でしたが、PyPyではCPython 2.6/2.7よりも たくさんの 場合に動作します。)
  • __builtins__ はいつも __builtin__ モジュールを参照しますので、 その辞書はCPythonにはありません。 __builtins__ への割り当ては影響がありません。
  • Do not compare immutable objects with is. For example on CPython it is true that x is 0 works, i.e. does the same as type(x) is int and x == 0, but it is so by accident. If you do instead x is 1000, then it stops working, because 1000 is too large and doesn’t come from the internal cache. In PyPy it fails to work in both cases, because we have no need for a cache at all.
  • Also, object identity of immutable keys in dictionaries is not necessarily preserved.