ホームページ > バックエンド開発 > Python チュートリアル > インタープリターの中のインタープリター

インタープリターの中のインタープリター

Susan Sarandon
リリース: 2024-12-15 20:22:11
オリジナル
119 人が閲覧しました

An interpreter inside an interpreter

開発を始めて数か月後、私はメンフィスの北極点として、Flask サーバーを完全にインタプリタ内で実行することだと決めました。これにどれだけの作業が必要になるか全く分かりませんでしたが、ただ、それがクールに聞こえ、おそらく途中で多くのことを学ぶだろうということだけを考えていました。もし私が今日この目標を立てるとしたら、FastAPI を選択するか、まったく何も選択しないかもしれません。それは愚かなことだからです。

Pythonの標準ライブラリ

私が直面した大きな決断は、Python 標準ライブラリをどのように扱うかということでした。ご存知かと思いますが、言語の標準ライブラリは、技術的には言語定義やランタイムの一部ではありません。言語とランタイムをより便利にするために、リリースに含まれています。スレッド化や非同期サポートのない Python を想像してみてください。式を評価してクラスをインスタンス化することは引き続き可能ですが、ほとんどの実稼働対応プログラムには、何らかの同時実行サポートが必要です。

1 つのオプションは、標準ライブラリ全体を自分で書き直すことです。私はインタプリタを構築しているんですよね?これは RustPython のアプローチであり、素晴らしい道だと思います。私は、ランタイムを動作させるだけで十分だと考え、あらゆる手抜きを探していたため、これをやめることにしました。

Python の標準ライブラリは、Python で実装された部分と C で実装された部分の 2 つの主要な部分で構成されています。便利なことに、私は独自の Python インタープリタを持っていました。前者を満たすために、ホスト マシンからの Python ソース ファイルを解釈するだけでよいでしょうか?はい、できました。彼らが使用するすべての構文と機能をサポートする必要がありますが、その後は問題なく動作します。

C パートが興味深いところです。はるか昔の 2023 年、私はその意味を十分に理解せずに、Python インタープリターを Python インタープリターの中に埋め込むことにしました。今度は、この問題について考え直して、このアプローチを続けるか、それとも別の道を選択するかを決定する時が来ました。

Rust と Python の相互運用ショップは Pyo3 です。町にある唯一のゲームである Pyo3 は、Foreign Function Interface (FFI) を使用して、Rust コードが CPython バイナリを呼び出すことを可能にします。これは、私が AMD でのキャリア中に使用した概念である Application Binary Interface (ABI) に同意することで機能します。コア ソフトウェア ftw!

モジュールのインポート

私の最初の使用例は、import sys を実行し、メンバー アクセス操作を実行できるオブジェクトを取得することでした。ここからは通訳の話に入りますが、これが私が話している種類の REPL セッションです。

Python 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)]
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys
<module 'sys' (built-in)>
>>> type(sys.modules)
<class 'dict'>
ログイン後にコピー
ログイン後にコピー

Pyo3 を使用してこの機能を取得するのは簡単でした。

pub struct CPythonModule(PyObject);

impl CPythonModule {
    pub fn new(name: &str) -> Self {
        pyo3::prepare_freethreaded_python();
        let pymodule = Python::with_gil(|py|
            PyModule::import(py, name).expect("Failed to import module").into()
        );

        Self(pymodule)
    }
}
ログイン後にコピー
ログイン後にコピー

これを使用して、メンフィスで同様の REPL セッションを実行できます。これを実行するための機能フラグの組み合わせを覚えていると仮定します。

Python 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)]
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys
<module 'sys' (built-in)>
>>> type(sys.modules)
<class 'dict'>
ログイン後にコピー
ログイン後にコピー

もしあなたが自問しているのなら、このアプローチを使って標準ライブラリ全体 (Python と C で書かれた部分を含む) をインポートして、あなたの人生、自由、幸福の追求全体をもっと簡単にできないだろうか。答えは「はい」です。それは有効なアプローチでしょう!しかし、それだと私のインタプリタは私が望んでいる以上に CPython を中心としたシェルになってしまいます。これは学習演習なので、私は恣意的な決定を歓迎します。 Memphis 内に CPython の一部をロードすると、Memphis は本物のインタープリタではなくなると主張する純粋主義者に対しては、私はこう言いたいと思います。「あなたのインタープリタを見せてください。

Memphis と CPython の両方を使用して REPL セッション内で import sys を実行し、htop で簡単なテストを実行しました。 Memphis では、これにより CPython ライブラリがメモリにロードされるため、RAM 使用量 (htop の常駐セット サイズ) が約 5MB 増加しました。比較のために、Memphis REPL は sys モジュールのロードで約 9MB の RAM を使用しますが、Python REPL は sys モジュールのロード前後でほぼ同じ RAM を使用します。これは同一の比較ではないと思いますが、少なくとも、メンフィスが私のコンピューターを徐々に窒息死させるつもりはないということはわかりました。

オブジェクトを変換して実存させる

この設定の次の複雑さは、Memphis オブジェクト表現を CPython 表現に変換すること、またはその逆の変換を必要とすることです。これは進行中の作業であり、私の主な指示は、当初は「失敗しないこと」、そして最近では「非可逆変換を実行する場合は警告を表示すること」でした。

これは、PyObject (Pyo3 側のオブジェクト表現) から ExprResult (メンフィス表現) への変換です。

pub struct CPythonModule(PyObject);

impl CPythonModule {
    pub fn new(name: &str) -> Self {
        pyo3::prepare_freethreaded_python();
        let pymodule = Python::with_gil(|py|
            PyModule::import(py, name).expect("Failed to import module").into()
        );

        Self(pymodule)
    }
}
ログイン後にコピー
ログイン後にコピー

そして、これが逆の比較です。どちらの場合も、CPython GIL (グローバル インタープリター ロック) へのアクセスを制御する Python オブジェクトを渡す必要があることに注意してください。

memphis 0.1.0 REPL (Type 'exit()' to quit)
>>> import sys
>>> sys
<module 'sys' (built-in)>
>>> type(sys.modules)
<class 'dict' (built-in)>
ログイン後にコピー

これは私がさらに探索したい豊かな領域です。私が検討した方向性のいくつかを以下に示します:

  1. オブジェクトが FFI インターフェースを通過するたびに変換します。 (そしてはい、頭字語が外部関数インターフェイス インターフェイスに拡張されることは認識しています。) これは、私がすでに行っていることの大まかなものです。私はそれを所有するだけでよく、詐欺師であるとは感じません。これは単純ですが非効率かもしれません。
  2. 各オブジェクトが各側に 1 回だけ存在するようにレジストリを保持します。これは (1) よりも効率的ですが、これらのオブジェクトを検索してリンクするために使用できる安定した値が必要になります。
  3. Rust 側で単一の表現を目指し、Pyo3 を使用して、必要に応じてフィールドをプロキシし、遅延変換します。これでも (1) の機能を活用できると思いますが、より効率的な方法で行われます。
  4. Memphis オブジェクトのメモリ レイアウトを PyObject のメモリ レイアウトと一致させます。 #[repr(C)] がすでに Rust で動作しているのと同様に、これは ABI が関数呼び出しに対して果たす役割に似ています。それぞれの側が何を評価する必要があるかが異なることを考えると、これが可能かどうかさえわかりませんが、これは私にとって興味深いものです。

今は C モジュールをほとんどロードできないので、先を進んでいますが、この分野で私の好奇心がどこへでも行き着くところは本当に終わりがありません。

終わり

Flask を起動するためにとぼとぼと進んでいるときに、新たな変換失敗に遭遇したとき、私はこれをつつき続けています。この演習は、すべてのオブジェクト (またはクラス、モジュールなど) がメモリ内に既知の形式で存在する属性のセットであることを思い出させるのに役立ちます。その形式を十分に理解していれば、メンフィス側か CPython 側かに関係なく、信じられないようなことができるはずです。

この哲学は、From Scratch Code での私の仕事にも原動力となります。コード内でライブラリを動作させることができないことにうんざりしている場合は、一歩下がって、「ライブラリは実際に何をしているのか?」と自問することをお勧めします。それは必要ですか? それとも、もっと簡単な解決策でうまくいくでしょうか?私は、ソフトウェアに対するこのような好奇心を育むことが重要であると信じています。そして、この考え方をあなたのツールボックスに組み込むお手伝いを喜んでさせていただきます。


このような投稿をさらに直接受信トレイに受け取りたい場合は、ここから購読できます!

他の場所

私はソフトウェア エンジニアの指導に加えて、成人と診断された自閉症者としての経験についても書いています。コードは減り、ジョークの数は同じです。

  • なぜ私は承認を求めるのでしょうか? - フロムスクラッチドット組織

以上がインタープリターの中のインタープリターの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート