なぜそんなに多くのPythonがあるのですか? Python実装の比較

公開: 2022-03-11

Pythonは素晴らしいです。

驚いたことに、それはかなりあいまいなステートメントです。 「Python」とはどういう意味ですか? Pythonは抽象インターフェースを意味しますか? 一般的なPython実装であるCPythonを意味しますか(同様の名前のCythonと混同しないでください)? それとも私は完全に何か他のものを意味しますか? たぶん、私はJython、IronPython、またはPyPyを斜めに参照しています。 あるいは、私は本当に深いところから離れて、RPythonまたはRubyPython(非常に非常に異なるもの)について話しているのかもしれません。

上記のテクノロジーは一般的に名前が付けられ、一般的に参照されていますが、それらのいくつかは完全に異なる目的を果たします(または、少なくとも、完全に異なる方法で動作します)。

Pythonインターフェースを使って作業している間、私はこれらの。*ythonツールをたくさん見つけました。 しかし、最近まで、私はそれらが何であるか、それらがどのように機能するか、そしてなぜそれらが(独自の方法で)必要であるかを理解するために時間をかけませんでした。

このチュートリアルでは、最初からさまざまなPython実装を見ていき、最後にPyPyの完全な紹介を行います。これは、この言語の未来であると私は信じています。

それはすべて、「Python」が実際に何であるかを理解することから始まります。

マシンコードや仮想マシンなどをよく理解している場合は、スキップしてください。

「Pythonは解釈またはコンパイルされていますか?」

これは、Python初心者にとってよくある混乱のポイントです。

比較するときに最初に気付くのは、「Python」がインターフェースであるということです。 Pythonが何をすべきか、そしてそれがどのように振る舞うべきか(他のインターフェースと同様に)の仕様があります。 そして、複数の実装があります(他のインターフェースと同様)。

次に気付くのは、「解釈された」と「コンパイルされた」は実装のプロパティであり、インターフェースではないということです。

したがって、質問自体は実際には整形式ではありません。

Pythonは解釈またはコンパイルされていますか? 質問は本当に整形式ではありません。

とは言うものの、最も一般的なPython実装(CPython:Cで記述され、単に「Python」と呼ばれることが多く、私が何について話しているのかわからない場合は確かに何を使用しているのか)の場合、答えは次のとおりです。 、いくつかのコンパイルを行います。 CPythonは*Pythonソースコードをバイトコードにコンパイルし、次にこのバイトコードを解釈して実行します。

*注:これは、従来の意味での「コンパイル」ではありません。 通常、「コンパイル」は高級言語を使用してそれを機械語に変換していると言えます。 しかし、それは一種の「コンパイル」です。

その答えをもっと詳しく見てみましょう。これは、投稿の後半で出てくる概念のいくつかを理解するのに役立ちます。

バイトコードとマシンコード

バイトコードとマシンコード(別名ネイティブコード)の違いを理解することは非常に重要です。おそらく、次の例で最もよく説明されています。

  • Cはマシンコードにコンパイルされ、マシンコードはプロセッサ上で直接実行されます。 各命令は、CPUにものを移動するように指示します。
  • Javaはバイトコードにコンパイルされます。バイトコードは、プログラムを実行するコンピューターの抽象化であるJava仮想マシン(JVM)で実行されます。 各命令は、コンピュータと対話するJVMによって処理されます。

簡単に言うと、マシンコードははるかに高速ですが、バイトコードはより移植性が高く安全です。

マシンコードはマシンによって異なりますが、バイトコードはすべてのマシンで同じように見えます。 マシンコードはセットアップに最適化されていると言う人もいるかもしれません。

CPythonの実装に戻ると、ツールチェーンのプロセスは次のとおりです。

  1. CPythonは、Pythonソースコードをバイトコードにコンパイルします。
  2. 次に、そのバイトコードがCPython仮想マシンで実行されます。
初心者は、Pythonが.pycファイルのためにコンパイルされていると考えることがよくあります。 それにはいくつかの真実があります。.pycファイルはコンパイルされたバイトコードであり、それが解釈されます。 したがって、以前にPythonコードを実行したことがあり、.pycファイルが手元にある場合は、バイトコードを再コンパイルする必要がないため、2回目はより高速に実行されます。

代替VM:Jython、IronPythonなど

先に述べたように、Pythonにはいくつかの実装があります。 繰り返しになりますが、前述のように、最も一般的なのはCPythonですが、この比較ガイドのために言及する必要のあるものもあります。 これはCで記述されたPython実装であり、「デフォルト」の実装と見なされます。

しかし、代替のPython実装についてはどうでしょうか? 最も有名なものの1つは、JVMを利用するJavaで記述されたPython実装であるJythonです。 CPythonはCPythonVMで実行するバイトコードを生成しますが、JythonはJVMで実行するJavaバイトコードを生成します(これは、Javaプログラムをコンパイルするときに生成されるものと同じものです)。

JythonによるJavaバイトコードの使用は、このPython実装図に示されています。

「なぜ別の実装を使用するのですか?」と質問するかもしれません。 たとえば、これらのさまざまなPython実装は、さまざまなテクノロジースタックとうまく連携します

CPythonを使用すると、PythonコードのC拡張機能を非常に簡単に記述できます。これは、最終的にはCインタープリターによって実行されるためです。 一方、Jythonを使用すると、他のJavaプログラムを非常に簡単に操作できます。Jythonプログラム内からJavaクラスを呼び出して利用することで、追加の作業なしで任意のJavaクラスをインポートできます。 (余談ですが、よく考えていない場合、これは実際にはくだらないことです。さまざまな言語を組み合わせてマッシュアップし、それらをすべて同じ内容にコンパイルできるようになりました。(Rostinが述べたように、 FortranとCコードの組み合わせはしばらく前から存在しているので、もちろん、これは必ずしも新しいものではありません。しかし、それでもかっこいいです。))

例として、これは有効なJythonコードです。

 [Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51 >>> from java.util import HashSet >>> s = HashSet(5) >>> s.add("Foo") >>> s.add("Bar") >>> s [Foo, Bar]

IronPythonは、もう1つの人気のあるPython実装であり、完全にC#で記述され、.NETスタックを対象としています。 特に、JVMに匹敵するMicrosoftの共通言語ランタイム(CLR)である.NET仮想マシンと呼ばれるもので実行されます。

Jython:Java :: IronPython:C#と言うかもしれません。 それらは同じそれぞれのVMで実行され、IronPythonコードからC#クラスをインポートし、JythonコードからJavaクラスをインポートすることができます。

CPython以外のPython実装に触れることなく、生き残ることは完全に可能です。 ただし、切り替えには利点があり、そのほとんどはテクノロジースタックに依存します。 多くのJVMベースの言語を使用していますか? Jythonはあなたのためかもしれません。 .NETスタックについてはどうですか? たぶんあなたはIronPythonを試してみるべきです(そして多分あなたはすでに持っています)。

このPython比較チャートは、Python実装間の違いを示しています。

ちなみに、これは別の実装を使用する理由にはなりませんが、これらの実装は、Pythonソースコードの処理方法を超えて実際には動作が異なることに注意してください。 ただし、これらの違いは通常は軽微であり、これらの実装が活発に開発されているため、時間の経過とともに解消または出現します。 たとえば、IronPythonはデフォルトでUnicode文字列を使用します。 ただし、CPythonはバージョン2.xではデフォルトでASCIIになります(非ASCII文字ではUnicodeEncodeErrorで失敗します)が、3.xではデフォルトでUnicode文字列をサポートします。

ジャストインタイムコンパイル:PyPyと未来

つまり、Pythonの実装はCで、1つはJavaで、もう1つはC#で記述されています。 次の論理的なステップ:Pythonで書かれたPython実装。 (教育を受けた読者は、これが少し誤解を招くことに気付くでしょう。)

ここで物事が混乱する可能性があります。 まず、ジャストインタイム(JIT)コンパイルについて説明します。

JIT:その理由と方法

ネイティブマシンコードはバイトコードよりもはるかに高速であることを思い出してください。 さて、バイトコードの一部をコンパイルして、それをネイティブコードとして実行できるとしたらどうでしょうか。 バイトコードをコンパイルするためにいくらかの代償(つまり、時間)を支払う必要がありますが、最終結果がより速い場合、それは素晴らしいことです! これが、インタプリタとコンパイラの利点を組み合わせたハイブリッド手法であるJITコンパイルの動機です。 基本的に、JITはコンパイルを利用してインタプリタシステムを高速化したいと考えています。

たとえば、JITが採用する一般的なアプローチは次のとおりです。

  1. 頻繁に実行されるバイトコードを特定します。
  2. ネイティブマシンコードにコンパイルします。
  3. 結果をキャッシュします。
  4. 同じバイトコードが実行されるように設定されている場合は常に、事前にコンパイルされたマシンコードを取得して、メリット(つまり、速度の向上)を享受してください。

これがPyPyの実装のすべてです。JITをPythonに導入します(これまでの取り組みについては付録を参照してください)。 もちろん、他の目標もあります。PyPyは、クロスプラットフォーム、メモリライト、スタックレスサポートを目指しています。 しかし、JITは本当にそのセールスポイントです。 一連のテストの平均として、パフォーマンスが6.27倍向上すると言われています。 内訳については、PyPySpeedCenterの次のチャートを参照してください。

PyPy実装を使用してJITをPythonインターフェースに導入すると、パフォーマンスが向上します。

PyPyは理解しにくい

PyPyには大きな可能性があり、現時点ではCPythonとの互換性が高いです(Flask、Djangoなどを実行できます)。

しかし、PyPyには多くの混乱があります(たとえば、PyPyPyを作成するためのこの無意味な提案を参照してください…)。 私の意見では、これは主にPyPyが実際には2つのものであるためです。

  1. RPythonで書かれたPythonインタープリター(Pythonではありません(以前は嘘をついていました))。 RPythonは、静的型付けを使用するPythonのサブセットです。 Pythonでは、型について厳密に推論することは「ほとんど不可能」です(なぜそれほど難しいのですか?次の事実を考慮してください。

     x = random.choice([1, "foo"])

    有効なPythonコードになります(Ademanへのクレジット)。 xのタイプは何ですか? 型が厳密に強制されていない場合、変数の型についてどのように推論できますか?) RPythonを使用すると、柔軟性がいくらか犠牲になりますが、代わりに、メモリ管理などについて推論するのがはるかに簡単になり、最適化が可能になります。

  2. さまざまなターゲットのRPythonコードをコンパイルし、JITを追加するコンパイラ。 デフォルトのプラットフォームはC、つまりRPython-to-Cコンパイラですが、JVMなどをターゲットにすることもできます。

このPython比較ガイドでわかりやすくするために、これらをPyPy(1)およびPyPy(2)と呼びます。

なぜこれら2つのものが必要なのですか、そしてなぜ同じ屋根の下にあるのですか? このように考えてください。PyPy(1)はRPythonで記述されたインタープリターです。 そのため、ユーザーのPythonコードを取り込んで、バイトコードにコンパイルします。 しかし、インタプリタ自体(RPythonで記述されている)は、実行するために別のPython実装によって解釈される必要がありますよね?

そうですね、CPythonを使用してインタープリターを実行することもできます。 しかし、それはそれほど速くはありません。

代わりに、PyPy(2)(RPythonツールチェーンと呼ばれる)を使用して、PyPyのインタープリターを別のプラットフォーム(C、JVM、CLIなど)のコードにコンパイルし、JITを次のように追加するという考え方です。良い。 それは魔法のようなものです。PyPyはJITをインタプリタに動的に追加し、独自のコンパイラを生成します。 (繰り返しになりますが、これはナッツです。インタプリタをコンパイルし、別のスタンドアロンコンパイラを追加しています。

最終的に、結果はPythonソースコードを解釈し、JIT最適化を活用するスタンドアロンの実行可能ファイルになります。 それがまさに私たちが望んでいたことです! 一口ですが、この図が役立つかもしれません。

この図は、インタープリター、コンパイラー、JITを使用した実行可能ファイルなどのPyPy実装の美しさを示しています。

繰り返しになりますが、PyPyの本当の美しさは、JITを気にせずに、RPythonでさまざまなPythonインタープリターを自分で作成できることです。 次に、PyPyはRPython Toolchain / PyPy(2)を使用してJITを実装します。

実際、さらに抽象化すれば、理論的には任意の言語のインタープリターを作成し、それをPyPyにフィードして、その言語のJITを取得できます。 これは、PyPyが、解釈する言語の詳細ではなく、実際のインタープリターの最適化に重点を置いているためです。

理論的には、任意の言語のインタープリターを作成し、それをPyPyにフィードして、その言語のJITを取得することができます。

簡単な余談として、JIT自体は絶対に魅力的です。 これは、トレースと呼ばれる手法を使用しており、次のように実行されます。

  1. インタプリタを実行し、すべてを解釈します(JITを追加しません)。
  2. インタプリタされたコードの簡単なプロファイリングを行います。
  3. 以前に実行した操作を特定します。
  4. これらのコードをマシンコードにコンパイルします。

さらに、この論文は非常にアクセスしやすく、非常に興味深いものです。

まとめ:PyPyのRPython-to-C(または他のターゲットプラットフォーム)コンパイラーを使用して、PyPyのRPythonで実装されたインタープリターをコンパイルします。

まとめ

Pythonの実装を長い間比較した後、私は自分自身に問いかけなければなりません。なぜこれがこんなに素晴らしいのでしょうか。 このクレイジーなアイデアを追求する価値があるのはなぜですか? Alex Gaynorは、彼のブログでそれをうまく表現していると思います。

要するに:

  • ソースコードを(JITを使用して)ネイティブコードにコンパイルするため、高速です
  • 追加の作業がほとんどなく、インタプリタにJITが追加されるため、柔軟性があります
  • RPythonでインタープリターを作成できるため、(これも)柔軟性があります。これは、たとえばCよりも拡張が簡単です(実際、独自のインタープリターを作成するためのチュートリアルがあるほど簡単です)。

付録:聞いたことがあるかもしれない他のPython名

  • Python 3000(Py3k):2008年に登場した後方互換性のない主要なPythonリリースであるPython 3.0の代替命名法。Py3kチームは、この新しいバージョンが完全に採用されるまでに約5年かかると予測しました。 そして、ほとんどの(警告:逸話的な主張)Python開発者はPython 2.xを使い続けていますが、人々はますますPy3kを意識しています。

  • Cython:C関数を呼び出すためのバインディングを含むPythonのスーパーセット。
    • 目標:PythonコードのC拡張機能を記述できるようにします。
    • また、既存のPythonコードに静的型付けを追加して、コンパイルしてCのようなパフォーマンスを実現することもできます。
    • これはPyPyに似ていますが、同じではありません。 この場合、コンパイラに渡す前に、ユーザーのコードを入力するように強制します。 PyPyを使用すると、単純な古いPythonを記述し、コンパイラーが最適化を処理します。

  • Numba:注釈付きのPythonコードにJITを追加する「ジャストインタイムの特殊コンパイラ」。 最も基本的な用語では、いくつかのヒントを与えると、コードの一部が高速化されます。 Numbaは、データ分析と管理のためのパッケージのセットであるAnacondaディストリビューションの一部として提供されます。

  • IPython:他の議論とは大きく異なります。 Python用のコンピューティング環境。 GUIツールキットやブラウザエクスペリエンスなどのサポートとインタラクティブ。

  • Psyco:Python拡張モジュールであり、初期のPythonJITの取り組みの1つです。 しかし、それ以来、「維持されておらず、死んでいる」とマークされています。 実際、Psycoのリード開発者であるArmin Rigoは、現在PyPyに取り組んでいます。

Python言語バインディング

  • RubyPython:RubyVMとPythonVMの間のブリッジ。 PythonコードをRubyコードに埋め込むことができます。 Pythonの開始位置と停止位置を定義すると、RubyPythonがVM間でデータをマーシャリングします。

  • PyObjc:PythonとObjective-Cの間の言語バインディングであり、それらの間のブリッジとして機能します。 実際には、これは、PythonコードからObjective-Cライブラリ(OS Xアプリケーションを作成するために必要なすべてのものを含む)を利用でき、Objective-CコードからPythonモジュールを利用できることを意味します。 この場合、CPythonはObjective-CのサブセットであるCで記述されていると便利です。

  • PyQt:PyObjcはOS X GUIコンポーネントのバインディングを提供しますが、PyQtはQtアプリケーションフレームワークに対して同じことを行い、リッチなグラフィックインターフェイスの作成、SQLデータベースへのアクセスなどを可能にします。Pythonのシンプルさを他のフレームワークにもたらすことを目的とした別のツール。

JavaScriptフレームワーク

  • pyjs(Pyjamas):PythonでWebおよびデスクトップアプリケーションを作成するためのフレームワーク。 PythonからJavaScriptへのコンパイラ、ウィジェットセット、およびその他のツールが含まれています。

  • Brython:Py3kコードをブラウザで実行できるようにするJavaScriptで記述されたPythonVM。