ホームページ バックエンド開発 Python チュートリアル Python バイトコードでネストされた関数のサポートを追加した方法

Python バイトコードでネストされた関数のサポートを追加した方法

Dec 31, 2024 pm 06:58 PM

How I added support for nested functions in Python bytecode

いくつかのかなりクールなものを共有したいと思いました。ネストされたサポートを追加した方法など、Python バイトコード について学習してきました。関数はありますが、印刷所の人は 500 ワード以内にする必要があると言っていました。

今週は休日ですと彼は肩をすくめました。 私に何を期待していますか?

コード スニペットを除く、私は交渉しました。

わかりました、彼は譲歩しました。

そもそもなぜバイトコードを使うか知っていますか?

私は印刷機を操作しているだけですが、あなたを信頼しています。

まあまあです。始めましょう。

そもそもなぜバイトコードを使うのか

Rust で書かれた私の Python インタプリタである Memphis には 2 つの実行エンジンがあります。どちらもすべてのコードを実行できるわけではありませんが、一部のコードは実行できます。

私の ツリーウォーク インタプリタ は、自分が何をしているのか分からない場合に構築するものです。 ?‍♂️ 入力 Python コードをトークン化し、抽象構文ツリー (AST) を生成し、ツリーをたどって 各ノードを評価します。式は値を返し、ステートメントはシンボル テーブルを変更します。シンボル テーブルは、Python スコープ ルールを尊重する一連のスコープとして実装されます。簡単な肺炎 LEGB を覚えておいてください: ローカル、エンクロージング、グローバル、ビルトイン。

私の バイトコード VM は、自分が何をしているのかは分からないが、知っているように行動したい場合に構築するものです。また?‍♂️。このエンジンでは、トークンと AST は同じように機能しますが、歩くのではなく全力疾走を始めます。 AST を、以降バイトコードとして知られる中間表現 (IR) にコンパイルします。次に、スタックベースの仮想マシン (VM) を作成します。これは概念的には CPU のように機能し、バイトコード命令を順番に実行しますが、完全にソフトウェアで実装されます。

(とりとめのない両方のアプローチの完全なガイドについては、Crafting Interpreters が優れています。)

そもそもなぜこれを行うのでしょうか?携帯性とパフォーマンスという 2 つの P を覚えておいてください。 2000 年代初頭、Java バイトコードの移植性について誰も黙らなかったことを覚えていますか? 必要なのは JVM だけで、どのマシンでもコンパイルされた Java プログラムを実行できます! Python は技術的およびマーケティング上の理由からこのアプローチを採用しませんでしたが、理論的には同じ原則が当てはまります。 (実際には、コンパイル手順が異なり、このワームの缶を開けたことを後悔しています。)

しかし、パフォーマンスが重要です。プログラムの存続期間中に AST を何度も走査するよりも、コンパイルされた IR の方が効率的な表現となります。 AST を繰り返し走査するオーバーヘッドを回避することでパフォーマンスが向上し、そのフラットな構造により、実行時の分岐予測とキャッシュの局所性が向上することがよくあります。

(コンピューター アーキテクチャのバックグラウンドがないのにキャッシュについて考えなかったということを責めるつもりはありません。私はその業界でキャリアをスタートしましたが、キャッシュについて考えることは、回避方法について考えるよりもはるかに少ないです。同じコード行を 2 回書くので、パフォーマンスに関しては私を信頼してください。それが私のリーダーシップ スタイルです。)

やあ、これは 500 単語です。フレームをロードしてリッピングする必要があります。

もう?!コード スニペットを除外しましたか?

コード スニペットはありません。

わかりました、わかりました。あと500個だけ。約束します。

Python 変数はコンテキストが重要

約 1 年前、バイトコードの VM 実装を表にまとめるまでにはかなりの時間がかかりました。Python の関数とクラスを定義し、それらの関数を呼び出し、それらのクラスをインスタンス化することができました。私はいくつかのテストでこの動作を取り締まりました。しかし、私の実装は雑で、さらに面白いものを追加する前に、基本を再検討する必要があることはわかっていました。クリスマス週間なので、楽しいことを追加したいと思います。

TODO に注目しながら、関数を呼び出すためのこのスニペットを考えてみましょう。

fn compile_function_call(
    &mut self,
    name: &str,
    args: &ParsedArguments)
) -> Result<Bytecode, CompileError> {
    let mut opcodes = vec![];

    // We push the args onto the stack in reverse call order so that we will pop
    // them off in call order.
    for arg in args.args.iter().rev() {
        opcodes.extend(self.compile_expr(arg)?);
    }

    let (_, index) = self.get_local_index(name);

    // TODO how does this know if it is a global or local index? this may not be the right
    // approach for calling a function
    opcodes.push(Opcode::Call(index));

    Ok(opcodes)
}
ログイン後にコピー
ログイン後にコピー

検討は終わりましたか?関数の引数をスタックにロードし、「関数を呼び出します」。バイトコードでは、すべての名前がインデックスに変換されます (VM 実行時のインデックス アクセスの方が速いため) が、ここでローカル インデックスを扱っているのかグローバル インデックスを扱っているのかを知る方法はありません。

次に、改良版について考えてみましょう。

fn compile_function_call(
    &mut self,
    name: &str,
    args: &ParsedArguments)
) -> Result<Bytecode, CompileError> {
    let mut opcodes = vec![self.compile_load(name)];

    // We push the args onto the stack in reverse call order so that we will pop
    // them off in call order.
    for arg in args.args.iter().rev() {
        opcodes.extend(self.compile_expr(arg)?);
    }

    let argc = opcodes.len() - 1;
    opcodes.push(Opcode::Call(argc));

    Ok(opcodes)
}
ログイン後にコピー

そのコードをご検討いただきありがとうございます。

入れ子になった関数呼び出しをサポートするようになりました。何が変わったのでしょうか?

  1. Call オペコードは、関数のインデックスではなく、いくつかの位置引数を取るようになりました。これにより、関数を呼び出す前にスタックからポップオフする引数の数が VM に指示されます。
  2. スタックから引数をポップした後、関数自体はスタック上に残り、compile_load はすでにローカル スコープとグローバル スコープを処理しています。

LOAD_GLOBAL と LOAD_FAST の比較

compile_load が何をしているのか見てみましょう。

fn compile_load(&mut self, name: &str) -> Opcode {
    match self.ensure_context() {
        Context::Global => Opcode::LoadGlobal(self.get_or_set_nonlocal_index(name)),
        Context::Local => {
            // Check locals first
            if let Some(index) = self.get_local_index(name) {
                return Opcode::LoadFast(index);
            }

            // If not found locally, fall back to globals
            Opcode::LoadGlobal(self.get_or_set_nonlocal_index(name))
        }
    }
}
ログイン後にコピー

ここではいくつかの重要な原則が機能しています:

  1. 現在のコンテキストに基づいて照合します。 Python セマンティクスに従って、Context::Global は任意のモジュール (スクリプトのエントリポイントだけでなく) のトップレベルにあり、Context::Local は任意のブロック (つまり、関数定義またはクラス定義) 内にあると考えることができます。
  2. ここで、ローカル インデックスと非ローカル インデックスを区別します。 (インデックス 0 がさまざまな場所で何を参照しているかを解読しようとして気が狂ってしまったので、型付き整数を導入しました。LocalIndex と NonlocalIndex は、型指定されていない符号なし整数に型安全性を提供します。これについては将来書くかもしれません!)
  3. 指定された名前のローカル変数が存在するかどうかはバイトコードのコンパイル時にわかります。存在しない場合は、実行時にグローバル変数を検索します。これは、Python に組み込まれたダイナミズムを物語っています。関数の実行時までにそのモジュールのグローバル スコープに変数が存在する限り、その値は実行時に解決できます。ただし、この動的な解像度にはパフォーマンスへの影響が伴います。ローカル変数のルックアップはスタック インデックスを使用するように最適化されていますが、グローバル ルックアップではグローバル名前空間ディクショナリを検索する必要があるため、時間がかかります。この辞書は名前とオブジェクトのマッピングであり、オブジェクト自体がヒープ上に存在する可能性があります。 「グローバルに考え、ローカルに行動する」という格言を誰が知っていたでしょうか。実際には Python スコープを参照していましたか?

変数名には何が含まれますか?

今日最後に説明するのは、これらの変数名がどのようにマップされるかを見てみることです。以下のコード スニペットでは、ローカル インデックスが code.varnames にあり、非ローカル インデックスが code.names にあることがわかります。どちらも、変数と名前のマッピングを含む、Python バイトコードのブロックのメタデータを含む CodeObject 上に存在します。

fn compile_function_call(
    &mut self,
    name: &str,
    args: &ParsedArguments)
) -> Result<Bytecode, CompileError> {
    let mut opcodes = vec![];

    // We push the args onto the stack in reverse call order so that we will pop
    // them off in call order.
    for arg in args.args.iter().rev() {
        opcodes.extend(self.compile_expr(arg)?);
    }

    let (_, index) = self.get_local_index(name);

    // TODO how does this know if it is a global or local index? this may not be the right
    // approach for calling a function
    opcodes.push(Opcode::Call(index));

    Ok(opcodes)
}
ログイン後にコピー
ログイン後にコピー

varname と names の違い (CPython では、これらを co_varnames と co_names と呼びます) に何週間も悩まされましたが、実際には非常に簡単です。 varnames は、指定されたスコープ内のすべてのローカル変数の変数名を保持し、names はすべての非ローカル変数に対して同じことを行います。

これを適切に追跡すると、他のすべては問題なく機能します。実行時に、VM は LOAD_GLOBAL または LOAD_FAST を認識し、それぞれグローバル名前空間ディクショナリまたはローカル スタックを参照することを認識します。

相棒!グーテンベルク氏が電話中で、もう印刷機を保持できないと言っています。

わかりました!大丈夫!わかった!発送しましょう。 ?

メンフィスの次は何でしょうか?

しー!印刷所の人は私が結論を書いていることを知らないので、手短に書きます。

変数のスコープと関数呼び出しがしっかりと確立されたので、スタック トレースや非同期サポートなどの機能に徐々に注意を向けています。バイトコードの詳細を楽しんでいただけた場合、または独自のインタプリタの構築について質問がある場合は、ぜひコメントをお寄せください。


購読して [何もなし] で保存

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

一緒に働きましょう

私はソフトウェア エンジニアを指導し、協力的な、時にはばかばかしい環境の中で技術的な課題とキャリアの成長を乗り越えていきます。ご興味がございましたら、ここからセッションをご予約ください。

他の場所

私はメンタリングに加えて、自営業や晩期に診断された自閉症を乗り越えた経験についても書いています。コードは減り、ジョークの数は同じです。

  • 湖効果コーヒー、第 2 章 - スクラッチドット組織から

以上がPython バイトコードでネストされた関数のサポートを追加した方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Python:ゲーム、GUIなど Python:ゲーム、GUIなど Apr 13, 2025 am 12:14 AM

PythonはゲームとGUI開発に優れています。 1)ゲーム開発は、2Dゲームの作成に適した図面、オーディオ、その他の機能を提供し、Pygameを使用します。 2)GUI開発は、TKINTERまたはPYQTを選択できます。 TKINTERはシンプルで使いやすく、PYQTは豊富な機能を備えており、専門能力開発に適しています。

Python vs. C:曲線と使いやすさの学習 Python vs. C:曲線と使いやすさの学習 Apr 19, 2025 am 12:20 AM

Pythonは学習と使用が簡単ですが、Cはより強力ですが複雑です。 1。Python構文は簡潔で初心者に適しています。動的なタイピングと自動メモリ管理により、使いやすくなりますが、ランタイムエラーを引き起こす可能性があります。 2.Cは、高性能アプリケーションに適した低レベルの制御と高度な機能を提供しますが、学習しきい値が高く、手動メモリとタイプの安全管理が必要です。

Pythonと時間:勉強時間を最大限に活用する Pythonと時間:勉強時間を最大限に活用する Apr 14, 2025 am 12:02 AM

限られた時間でPythonの学習効率を最大化するには、PythonのDateTime、時間、およびスケジュールモジュールを使用できます。 1. DateTimeモジュールは、学習時間を記録および計画するために使用されます。 2。時間モジュールは、勉強と休息の時間を設定するのに役立ちます。 3.スケジュールモジュールは、毎週の学習タスクを自動的に配置します。

Python vs. C:パフォーマンスと効率の探索 Python vs. C:パフォーマンスと効率の探索 Apr 18, 2025 am 12:20 AM

Pythonは開発効率でCよりも優れていますが、Cは実行パフォーマンスが高くなっています。 1。Pythonの簡潔な構文とリッチライブラリは、開発効率を向上させます。 2.Cのコンピレーションタイプの特性とハードウェア制御により、実行パフォーマンスが向上します。選択を行うときは、プロジェクトのニーズに基づいて開発速度と実行効率を比較検討する必要があります。

Python Standard Libraryの一部はどれですか:リストまたは配列はどれですか? Python Standard Libraryの一部はどれですか:リストまたは配列はどれですか? Apr 27, 2025 am 12:03 AM

PythonListSarePartOfThestAndardarenot.liestareBuilting-in、versatile、forStoringCollectionsのpythonlistarepart。

Python:自動化、スクリプト、およびタスク管理 Python:自動化、スクリプト、およびタスク管理 Apr 16, 2025 am 12:14 AM

Pythonは、自動化、スクリプト、およびタスク管理に優れています。 1)自動化:OSやShutilなどの標準ライブラリを介してファイルバックアップが実現されます。 2)スクリプトの書き込み:Psutilライブラリを使用してシステムリソースを監視します。 3)タスク管理:スケジュールライブラリを使用してタスクをスケジュールします。 Pythonの使いやすさと豊富なライブラリサポートにより、これらの分野で優先ツールになります。

Pythonの学習:2時間の毎日の研究で十分ですか? Pythonの学習:2時間の毎日の研究で十分ですか? Apr 18, 2025 am 12:22 AM

Pythonを1日2時間学ぶだけで十分ですか?それはあなたの目標と学習方法に依存します。 1)明確な学習計画を策定し、2)適切な学習リソースと方法を選択します。3)実践的な実践とレビューとレビューと統合を練習および統合し、統合すると、この期間中にPythonの基本的な知識と高度な機能を徐々に習得できます。

Python vs. C:重要な違​​いを理解します Python vs. C:重要な違​​いを理解します Apr 21, 2025 am 12:18 AM

PythonとCにはそれぞれ独自の利点があり、選択はプロジェクトの要件に基づいている必要があります。 1)Pythonは、簡潔な構文と動的タイピングのため、迅速な開発とデータ処理に適しています。 2)Cは、静的なタイピングと手動メモリ管理により、高性能およびシステムプログラミングに適しています。

See all articles