30. バイトコード命令
#30. バイトコード命令
バイトコード命令は、CPython 評価ループによって実行される操作です。これらは、解析、AST 構築、シンボル分析、コンパイル後の Python コードのコンパクトなインタープリター レベルの形式です。
次のような Python 関数:```python id="ya54lv" def add(a, b): return a + b
そのストリームを検査するには、`dis`:
```python id="7yf46p"
import dis
def add(a, b):
return a + b
dis.dis(add)
```出力は Python のバージョンによって異なりますが、通常は次のような指示が表示されます。```text id="ot1vx4"
LOAD_FAST
LOAD_FAST
BINARY_OP
RETURN_VALUE
```これらの命令は CPython 仮想マシンの語彙です。
## 30.1 バイトコード命令とは
バイトコード命令は、インタプリタに 1 つの小さな操作を実行するように指示します。
例:```text id="4m3y16"
load a local variable
load a constant
store into a local variable
perform binary addition
call a function
jump to another instruction
return from a frame
raise an exception
build a list
load an attribute
```命令は通常、次の 2 つの部分で構成されます。```text id="3pmt1a"
opcode
operand
```オペコードは何をすべきかを示します。
オペランドは、必要に応じて小さな整数の引数を提供します。
例えば:```text id="7i5m11"
LOAD_FAST 0
```手段:```text id="ssxj7c"
load fast local variable at slot 0
```そして:```text id="po4eiq"
LOAD_CONST 1
```手段:```text id="056i57"
load constant at index 1 in the code object's constants table
```一部の命令には意味のあるオペランドがありません。一部のオペランドには、その意味がオペコードに完全に依存するものがあります。
## 30.2 バイトコードはコードオブジェクト内に存在します
バイトコードは code オブジェクトに属します。
関数オブジェクトはコード オブジェクトを指します。```python id="3zs8ni"
def f(x):
return x + 1
print(f.__code__)
```コード オブジェクトには、命令ストリームと命令で使用されるテーブルが含まれています。
重要なコード オブジェクト データには次のものが含まれます。
|フィールド |目的 |
|---|---|
|`co_code`| Python に公開されるバイトコード表現 |
|`co_consts`|で使用される定数`LOAD_CONST` |
| `co_names`|グローバル、属性、およびインポート操作で使用される名前 |
|`co_varnames`|高速ローカル変数名 |
|`co_freevars`|外部スコープから変数を解放する |
|`co_cellvars`|内部関数によってキャプチャされたローカル |
|`co_stacksize`|スタック深さの最大値 |
|`co_flags`|実行フラグ |
|`co_filename`|ソースファイル名 |
|`co_name`|コードオブジェクト名 |
|`co_qualname`|修飾名 |
|例外テーブル |例外ハンドラーのメタデータ |
|ラインテーブル |ソース位置メタデータ |
バイトコード ストリームは、フルネーム、定数、またはオブジェクト ポインタを直接保存しないため、コンパクトです。これらのテーブルに整数インデックスを格納します。
## 30.3 手順は表を参照
次のことを考慮してください。```python id="p7suvh"
def f(x):
return x + 10
```定数`10`コードオブジェクトの定数テーブルに保存されます。
地元の名前`x`ローカル変数テーブルに格納されます。
命令ストリームはインデックスによってそれらを参照します。
概念的には:```text id="d913xq"
co_consts:
[None, 10]
co_varnames:
["x"]
bytecode:
LOAD_FAST 0
LOAD_CONST 1
BINARY_OP +
RETURN_VALUE
```この設計により、命令は小さく抑えられます。
文字列を保存する代わりに`"x"`すべてのローカル アクセス命令で、CPython はスロット番号を保存します`0`。
オブジェクトを保存する代わりに`10`CPython は命令内に直接定数インデックスを格納します`1`。
## 30.4 分解
の`dis`モジュールはバイトコードを読み取り可能な形式に変換します。```python id="45j36i"
import dis
def f(a, b):
c = a + b
return c
dis.dis(f)
```通常、分解には次の作業が含まれます。```text id="zxtwgj"
source line number
bytecode offset
opcode name
operand
resolved operand meaning
jump target markers
cache entries, depending on options and version
```形状の例:```text id="pely0k"
3 0 RESUME 0
2 LOAD_FAST 0 (a)
4 LOAD_FAST 1 (b)
6 BINARY_OP 0 (+)
10 STORE_FAST 2 (c)
4 12 LOAD_FAST 2 (c)
14 RETURN_VALUE
```正確な出力は Python のバージョンによって異なります。バイトコードは CPython 実装の詳細であり、安定した外部命令セットではありません。
## 30.5 命令オフセット
各命令にはバイトコード ストリーム内での位置があります。
逆アセンブリでは、これがオフセットとして表示されます。```text id="p6y2o6"
0 RESUME
2 LOAD_FAST
4 LOAD_FAST
6 BINARY_OP
10 STORE_FAST
```オフセットは以下によって使用されます。```text id="qv3e7s"
jump instructions
exception tables
line number mapping
tracebacks
debuggers
profilers
coverage tools
```ジャンプ命令は別のオフセットをターゲットにする場合があります。
概念的には:```text id="3ew1cz"
POP_JUMP_IF_FALSE 24
```手段:```text id="c0kqvz"
if condition is false, continue execution at bytecode offset 24
```最新の CPython の詳細は異なりますが、考え方は同じです。つまり、バイトコードはアドレス指定可能な命令のシーケンスです。
## 30.6 スタック効果
すべての命令にはスタック効果があります。
スタック効果は、命令がフレームの値スタックをどのように変更するかを表します。
|指示 |前にスタック | | の後にスタック
|---|---|---|
|`LOAD_CONST` | `[]` | `[constant]` |
| `LOAD_FAST` | `[]` | `[local]` |
| `STORE_FAST` | `[value]` | `[]` |
| `BINARY_OP` | `[left, right]` | `[result]` |
| `LOAD_ATTR` | `[object]` | `[attribute]` |
| `CALL` | `[callable, args...]` | `[result]` |
| `RETURN_VALUE` | `[value]`|フレームを終了します |
コンパイラは、有効なスタック規則を使用してバイトコードを出力する必要があります。すべての命令において、スタックには命令が予期する値が含まれている必要があります。
制御フローのマージ ポイントでは、すべての受信パスが互換性のあるスタック形状を生成する必要があります。
## 30.7 基本的なロード命令
ロード命令は値をスタックにプッシュします。
一般的な負荷カテゴリ:
|指示 |意味 |
|---|---|
|`LOAD_CONST`|から定数をプッシュします`co_consts` |
| `LOAD_FAST`| Push a local variable from a fast local slot |
|`LOAD_GLOBAL`|グローバル名または組み込み名をプッシュする |
|`LOAD_DEREF`|クロージャーセルの値をプッシュする |
|`LOAD_ATTR`|オブジェクトから属性をプッシュする |
|`LOAD_NAME`|クラスまたは動的名前空間検索を使用して名前をプッシュします。
例:```python id="0nyz5p"
def f(x):
return x + 1
```概念的なバイトコード:```text id="zc9cpj"
LOAD_FAST x
LOAD_CONST 1
BINARY_OP +
RETURN_VALUE
LOAD_FAST現在のフレームからスロットを読み取ります。LOAD_CONSTコードオブジェクトから読み取ります。
30.8 ストア手順
ストア命令はスタックから値を消費し、それらをどこかに配置します。
| 指示 | 意味 |
|---|---|
STORE_FAST |
高速ローカルスロットに保存 |
STORE_GLOBAL |
グローバル名前空間に保存 |
STORE_NAME |
現在のローカル名前空間に保存 |
STORE_ATTR |
オブジェクト属性に格納 |
STORE_SUBSCR |
サブスクリプション ターゲットに保存 |
STORE_DEREF |
クロージャセルに格納 |
DELETE_FAST |
ローカルスロットを削除する |
DELETE_ATTR |
属性を削除する |
DELETE_SUBSCR |
項目を削除する |
例:python id="dvy1us" def f(a, b): c = a + b return c 概念的なスタック動作:```text id="env4qv"
LOAD_FAST a stack: [a]
LOAD_FAST b stack: [a, b]
BINARY_OP + stack: [result]
STORE_FAST c stack: []
`STORE_FAST`結果を消費します。コンパイラが別の使用のために明示的に複製しない限り、割り当てられた値はスタックに残りません。
## 30.9 ローカル変数の命令
高速ローカル命令では、名前ではなくインデックスが使用されます。
のために:```python id="dse6a4"
def f(a, b):
c = a + b
return c
```コンパイラはローカル スロットを割り当てます。
|スロット |名前 |
|---:|---|
| 0 |`a`|
| 1 |`b`|
| 2 |`c`|
指示:```text id="wz4dzl"
LOAD_FAST 1
```手段:```text id="nxyj1j"
push local slot 1, which is b
```これは辞書を引くよりもはるかに安価です。フレームは、配列のようなレイアウトで高速ローカルを格納します。
## 30.10 定数命令
Constants are stored in`co_consts`。
例:```python id="j8ju43"
def f():
return 123
```概念的なコードオブジェクト:```text id="2kqu9y"
co_consts:
[None, 123]
bytecode:
LOAD_CONST 1
RETURN_VALUE
```定数テーブルには以下を含めることができます。```text id="b0tjvm"
None
numbers
strings
bytes
tuples of constants
frozensets of constants
nested code objects
```ネストされた関数と内包表記は、多くの場合、内部でネストされたコード オブジェクトとして表示されます。`co_consts`。
## 30.11 名前の指示
名前の検索はスコープによって異なります。
モジュールレベル:```python id="axbo96"
x = 10
print(x)
```名前はモジュール辞書に存在します。
関数内:```python id="0nxa3s"
def f():
return x
```もし`x`がローカルではない場合、CPython はグローバルまたは組み込みのルックアップを実行します。
重要な指示:
|指示 |一般的な使用法 |
|---|---|
|`LOAD_GLOBAL`|関数のグローバルおよび組み込みのルックアップ |
|`LOAD_NAME`|クラス本体と動的名前空間の検索 |
|`STORE_NAME`|モジュールまたはクラスの名前空間の割り当て |
|`LOAD_FAST`|関数ローカルスロットルックアップ |
|`LOAD_DEREF`|クロージャ変数の検索 |
コンパイラは、シンボル テーブル分析に基づいて命令を選択します。
## 30.12 属性の説明
属性アクセスには次のような命令が使用されます。`LOAD_ATTR`そして`STORE_ATTR`。```python id="vqlcss"
value = obj.x
```概念的には:```text id="so9d8z"
LOAD_FAST obj
LOAD_ATTR x
STORE_FAST value
```属性検索には以下が含まれる場合があります。```text id="bq7pdp"
object type
descriptor protocol
instance dictionary
slots
class dictionary
base classes
custom __getattribute__
custom __getattr__
inline caches
```しかし、バイトコードレベルのスタック効果は単純です。```text id="nxg6f5"
LOAD_ATTR:
input: [object]
output: [attribute_value]
```割り当ての場合:```python id="5m00ah"
obj.x = value
```概念的には:```text id="y7heq6"
LOAD_FAST value
LOAD_FAST obj
STORE_ATTR x
```正確なオペランドの順序は、オペコードの実装によって定義されます。
## 30.13 添え字の命令
サブスクリプションはスタック オペランドを使用します。```python id="0323zx"
value = xs[i]
```概念的なバイトコード:```text id="kvxa2c"
LOAD_FAST xs
LOAD_FAST i
BINARY_SUBSCR
STORE_FAST value
```の`BINARY_SUBSCR`命令はコンテナとキーを消費し、結果をプッシュします。
割り当ての場合:```python id="tnoqbn"
xs[i] = value
```概念的には:```text id="ey4z88"
LOAD_FAST value
LOAD_FAST xs
LOAD_FAST i
STORE_SUBSCR
```これにより、オブジェクトの項目割り当てプロトコルが呼び出されます。
削除の場合:```python id="edq3j2"
del xs[i]
```コンパイラは削除指向のサブスクリプション バイトコードを生成します。
## 30.14 二項演算
最新の CPython の使用法`BINARY_OP`多くの二項演算の場合、特定の演算を記述するオペランドを使用します。
Python 式:```python id="dgwszy"
a + b
```概念的なバイトコード:```text id="g1haxi"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
```その他の操作には次のようなものがあります。```text id="7wzr1z"
+
-
*
@
/
%
//
**
<<
>>
&
|
^
```インプレース バリアントも概念的には存在します。```python id="zg641m"
x += y
```これには、インプレース操作形式またはインプレース セマンティクスを試みるオペランド バリアントが使用される場合があります。
二項演算は動的です。`+`整数加算、浮動小数点加算、文字列連結、リスト連結、またはユーザー定義を意味する場合があります`__add__`。
## 30.15 単項演算
単項演算は 1 つのスタック値を消費し、1 つの結果をプッシュします。
例:```python id="45q3s0"
-x
+x
~x
not x
```概念的なバイトコード:```text id="0wm8tb"
LOAD_FAST x
UNARY_NEGATIVE
```スタック効果:```text id="4h9g0l"
input: [x]
output: [-x]
```単項演算では引き続き Python オブジェクト セマンティクスが使用されます。`-x`電話するかもしれない`x.__neg__()`ユーザー定義オブジェクトの場合。
## 30.16 比較手順
比較はオペランドを消費し、結果をプッシュします。```python id="kybtui"
a < b
```概念的には:```text id="lpn8y7"
LOAD_FAST a
LOAD_FAST b
COMPARE_OP <
```比較演算には次のものが含まれます。```text id="viop49"
<
<=
==
!=
>
>=
in
not in
is
is not
exception matching
```比較ではユーザーコードが呼び出される場合があります。```python id="iwk7b1"
class X:
def __lt__(self, other):
return True
```したがって、比較命令であっても、真実をテストする前に、一部のプロトコル コンテキストで割り当て、Python コードの呼び出し、例外の発生、非ブール オブジェクトの返しが行われる可能性があります。
## 30.17 ジャンプ命令
ジャンプ命令は命令ポインタを変更します。
彼らは以下を実装します:```text id="td78o9"
if statements
while loops
for loops
boolean short-circuiting
conditional expressions
exception flow
pattern matching branches
```例:```python id="gtmgg4"
def f(x):
if x:
return 1
return 0
```概念的なバイトコード:```text id="gsgii8"
LOAD_FAST x
POP_JUMP_IF_FALSE else_branch
LOAD_CONST 1
RETURN_VALUE
else_branch:
LOAD_CONST 0
RETURN_VALUE
```一部のジャンプは無条件です。上位スタックの値をテストするものもあります。値を保存するものもあります。ポップする人もいます。
ジャンプのスタック効果は、ターゲットと同じくらい重要です。
## 30.18 ループ命令
ループでは、ジャンプ命令とイテレータ固有の命令を使用します。
あ`while`ループ:```python id="1vfdnh"
while cond:
body()
```概念的には次のようにコンパイルされます。```text id="kbpb8k"
loop_start:
evaluate cond
if false, jump loop_end
execute body
jump loop_start
loop_end:
```あ`for`ループ:```python id="5q6rsq"
for x in xs:
body(x)
```イテレータプロトコル命令を使用します。```text id="x43wuw"
LOAD_FAST xs
GET_ITER
loop:
FOR_ITER end
STORE_FAST x
...
JUMP loop
end:
FOR_ITER反復が続く間、反復子をスタック上に保持します。
30.19 呼び出し命令
呼び出しは、最もパフォーマンスが重要なバイトコード操作の 1 つです。
のために:python id="e7dc4b" result = f(a, b) 概念的なスタックのセットアップ:```text id="a7s51h"
LOAD_FAST f
LOAD_FAST a
LOAD_FAST b
CALL 2
STORE_FAST result
通話は以下を対象とする可能性があります:```text id="t77949"
Python functions
built-in functions
bound methods
classes
callable instances
C extension functions
coroutines
descriptors
```CPython は、vectorcall などの最適化された呼び出し規則を使用して、一時的なタプルや辞書の作成を削減します。
## 30.20 返品手順
return 命令は現在のフレームを終了します。```python id="k8kmjw"
def f():
return 42
```概念的なバイトコード:```text id="tq86ob"
LOAD_CONST 42
RETURN_VALUE
```戻る前のスタック:```text id="5ks5b0"
[42]
RETURN_VALUE戻りオブジェクトを消費し、呼び出し元に渡します。
明示的な戻りのない関数の場合:python id="j1sx5u" def f(): pass コンパイラは次の戻り値を発行しますNone。
概念的には:```text id="x839pp" LOAD_CONST None RETURN_VALUE
例外の発生にもバイトコードが使用されます。```python id="c411i6"
raise ValueError("bad")
```概念的には:```text id="2swrvt"
LOAD_GLOBAL ValueError
LOAD_CONST "bad"
CALL 1
RAISE_VARARGS 1
```raise 命令は通常の実行を終了し、例外の伝播に入ります。
以下と対話する必要があります。```text id="nkhmtx"
thread exception state
tracebacks
exception handlers
finally blocks
context and cause
frame unwinding
```通常、raise 命令は通常の結果をプッシュしません。
## 30.22 例外処理命令
例外処理バイトコードは、Python のバージョン間で大幅に変更されました。
最新の CPython は、多くのタスクに対して古いブロック スタック オペコードではなく、コード オブジェクトに関連付けられた例外テーブルを使用します。
それでも、インタプリタには次の指示とメタデータが必要です。```text id="3wj1o7"
entering handlers
matching exception types
binding exception variables
reraising
clearing exception state
running finally blocks
handling with-statements
```例:```python id="7pkq92"
try:
risky()
except ValueError as exc:
recover(exc)
```コンパイルされたコードには次の内容を記述する必要があります。```text id="fxm4bb"
protected bytecode range
handler target
stack depth to restore
exception matching operation
binding of exc
cleanup of exc
```例外バイトコードは、一時スタック値を正しくクリーンアップしながら Python セマンティクスを保持する必要があるため、デリケートです。
## 30.23 インポート手順
インポートには専用のバイトコード操作があります。```python id="gik0n1"
import math
```概念的には:```text id="m9nu5u"
LOAD_CONST level
LOAD_CONST fromlist
IMPORT_NAME math
STORE_NAME math
```のために:```python id="zoxgo7"
from math import sqrt
```概念的な操作には次のものが含まれます。```text id="n577z3"
IMPORT_NAME math
IMPORT_FROM sqrt
STORE_NAME sqrt
```インポート バイトコードはインポート機構を呼び出します。モジュール コードの実行、インポート ロックの取得、キャッシュされたバイトコードの読み込み、パッケージの初期化の実行、またはインポート エラーの発生が発生する可能性があります。
import ステートメントは実行可能コードであり、静的宣言ではありません。
## 30.24 コンテナの説明
コンテナリテラルはビルド命令を使用します。```python id="fofmnt"
xs = [a, b, c]
```概念的には:```text id="8ezo7g"
LOAD_FAST a
LOAD_FAST b
LOAD_FAST c
BUILD_LIST 3
STORE_FAST xs
```他のビルド手順には、次の操作が含まれます。```text id="xge9ra"
tuples
sets
dicts
slices
strings
lists from comprehensions
maps from key-value pairs
```辞書リテラルの場合:```python id="2pyoy0"
d = {"x": a, "y": b}
```コンパイラはキーと値を配置して、辞書構築命令がそれらを使用できるようにします。
## 30.25 開梱手順
アンパック命令は反復可能な値を分解します。```python id="75qhe0"
a, b = pair
```概念的には:```text id="h2jtqv"
LOAD_FAST pair
UNPACK_SEQUENCE 2
STORE_FAST a
STORE_FAST b
```拡張開梱:```python id="b1ifoe"
a, *middle, z = values
```スター付きターゲットのリストを生成できるアンパック命令を使用します。
解凍手順では、正しいアリティを強制する必要があります。 iterable の値が少なすぎるか多すぎる場合、CPython はエラーを発生させます。
## 30.26 閉鎖手順
入れ子関数にはクロージャ関連の命令が必要です。
例:```python id="ji8q8n"
def outer():
x = 10
def inner():
return x
return inner
```コンパイラは次のように調整する必要があります`x`セルオブジェクトの中に住むこと。
関連する操作には次のものが含まれます。```text id="miqfx4"
make cell variables
load closure cells
load dereferenced values
store dereferenced values
build function with closure
```概念的には:```text id="xdtmaw"
outer frame:
x stored in cell
inner function:
closure points to same cell
inner bytecode:
LOAD_DEREF x
```このようにして`inner`アクセスできる`x`後`outer`戻ります。
## 30.27 関数の作成手順
あ`def`ステートメントは実行時に関数オブジェクトを作成します。```python id="az4vj6"
def f(x):
return x + 1
```モジュールの実行時、CPython は単に静的関数を登録するわけではありません。コード オブジェクトから関数オブジェクトを構築するバイトコードを実行します。
概念的には:```text id="oxvnd7"
LOAD_CONST <code object f>
MAKE_FUNCTION
STORE_NAME f
```関数にデフォルト、アノテーション、キーワードのデフォルト、またはクロージャ セルがある場合、それらは関数の作成時にロードされてアタッチされます。
これはその理由を説明します`def`実行可能です:```python id="vb8p68"
if debug:
def f():
return "debug"
else:
def f():
return "normal"
```1 つのブランチのみが作成およびバインドされます`f`。
## 30.28 クラス作成手順
あ`class`ステートメントはバイトコードも実行します。```python id="4c6kdt"
class C:
x = 1
```概念的な高レベルの動作:```text id="n94lef"
load build_class
load class body code object
make function for class body
load class name
call build_class
store class object
```クラス本体自体には code オブジェクトがあります。準備された名前空間で実行されます。完了後、メタクラスは実際のクラス オブジェクトを作成します。
これは、クラス本体が任意のコードを実行できる理由を説明しています。```python id="x8cd5y"
class C:
print("building class")
x = compute()
```評価ループは、他のコードと同様にその本体を実行します。
## 30.29 ジェネレーターとコルーチンの命令
ジェネレーターとコルーチンには、一時停止と再開の指示が必要です。
例:```python id="0q3kic"
def gen():
yield 1
yield 2
```あ`yield`命令はフレームの状態を維持しながら呼び出し元に値を返します。
概念的には:```text id="47ev86"
LOAD_CONST 1
YIELD_VALUE
resume later
LOAD_CONST 2
YIELD_VALUE
resume later
LOAD_CONST None
RETURN_VALUE
```コルーチンは関連するメカニズムを使用して、`await`。```python id="sbeqhd"
async def f():
result = await g()
return result
```バイトコードは以下をサポートする必要があります。```text id="b2khca"
creating coroutine objects
awaiting awaitables
suspending execution
resuming with values
resuming with exceptions
returning final result
```## 30.30 パターンマッチング命令
構造パターン マッチングは、特殊なテスト、アンパック、属性アクセス、マッピング チェック、シーケンス チェック、および分岐にコンパイルされます。
例:```python id="1rrlbw"
match value:
case [x, y]:
return x + y
case _:
return 0
```コンパイラは、大まかに次のことを行うバイトコードを出力します。```text id="6oglmv"
load subject
check sequence pattern
check length
unpack values
bind x and y
execute body
otherwise try next case
```パターン マッチング バイトコードでは、失敗したマッチングに関する Python セマンティクスを保持する必要があります。失敗した代替案からのバインディングが、その後成功したケースに誤って漏れてはなりません。
## 30.31 キャッシュ命令
最新の CPython には、一部のバイトコード命令に関連付けられたインライン キャッシュ エントリが含まれています。
逆アセンブリでは、オプションとバージョンに応じてキャッシュ関連のエントリが表示される場合があります。
これらのキャッシュ エントリは、次のような操作の特殊化をサポートします。```text id="j0ixza"
attribute access
global lookup
binary operations
method calls
function calls
subscript operations
```キャッシュ エントリは通常の Python 操作ではありません。これらはインタープリターのメタデータです。
論理命令のスタック効果は、依然として重要なセマンティック部分です。
例えば:```text id="8sx1vs"
LOAD_ATTR name
CACHE
CACHE
LOAD_ATTR引き続きオブジェクトを消費し、属性値をプッシュします。キャッシュ エントリは、これを高速化するのに役立ちます。
30.32 アダプティブ命令
アダプティブ バイトコードにより、CPython はホット操作を特殊化できます。
一般的な操作は、繰り返し実行された後、書き換えられるか、特殊な形式として解釈される場合があります。
概念的なフローの例:text id="08ouu3" BINARY_OP observes int + int repeatedly ↓ specialized int-add path 特殊な命令は同じスタック コントラクトを保持する必要があります。```text id="8f1uvg"
input: [left, right]
output: [result]
このメカニズムにより、Python レベルのセマンティクスを変更することなくパフォーマンスが向上します。
## 30.33 疑似命令
一部の命令名は、コンパイラ内部または生成されたメタデータには表示されますが、最終的なバイトコード ストリームでは通常のランタイム オペコードとしては表示されません。
疑似命令は次のことを表現するのに役立ちます。```text id="5ql4dw"
abstract control-flow operations
exception handling structure
compiler intermediate forms
assembler-level markers
```CPython の内部を読み取るときは、以下を区別してください。```text id="ccdgxn"
source-level syntax
compiler intermediate instructions
runtime bytecode instructions
inline cache entries
generated metadata
```オペコード関連ファイル内のすべての名前が、評価ループによって実行される通常の命令のように動作するわけではありません。
## 30.34 命令ファミリー
多くの指示は家族に属します。
例:```text id="36dyw4"
load family
store family
delete family
binary operation family
unary operation family
jump family
call family
import family
closure family
container-build family
exception family
```命令ファミリーは、逆アセンブリを読むのに役立ちます。
見ると`LOAD_*`、値がプッシュされることが期待されます。
見ると`STORE_*`、消費される値を期待します。
見ると`JUMP_*`、制御フローが変更されることが予想されます。
見ると`CALL`、呼び出し可能および引数スタックのレイアウトが重要であることが予想されます。
## 30.35 スタック中立命令
一部の命令は、論理 Python 値スタックを変更しません。
例としては次のものが挙げられます。```text id="cos45t"
RESUME
NOP
cache-related entries
some instrumentation markers
```これらの命令は、実行、トレース、特殊化、またはインタプリタの状態にとって依然として重要である可能性があります。
スタック中立命令は、Python オブジェクトをプッシュまたはポップしない場合でも、実行時の動作に影響を与える可能性があります。
例えば、`RESUME`最新の CPython バイトコードで実行再開ポイントをマークします。
## 30.36 バージョンの違い
CPython バイトコードはバージョン間で異なります。
変更には次のものが含まれる場合があります。```text id="kiol94"
new opcodes
removed opcodes
combined opcodes
different call protocol
different exception handling representation
different cache layout
different jump semantics
different disassembly format
specialized instruction changes
```これが、バイトコードをバージョン固有のものとして扱う必要がある理由です。
正確なバイトコードに依存するコードは、対象とする Python バージョンを宣言する必要があります。
バージョン依存ツールの例:```text id="yl9axc"
bytecode transformers
coverage tools
debuggers
decompilers
optimizers
security analyzers
teaching visualizers
profilers
```通常の Python アプリケーション コードの場合、バイトコードの詳細は通常は関係ありません。 CPython 内部の場合、これらは中心となります。
## 30.37 バイトコードを手動で読み取る
便利な読書プロセス:```text id="4mbysd"
identify locals
identify constants
identify names
track stack effects
mark jumps
mark call sites
mark exception regions
mark return paths
```例:```python id="9opwew"
def f(a, b):
if a > b:
return a - b
return b - a
```概念的なバイトコード:```text id="c9ffv9"
LOAD_FAST a
LOAD_FAST b
COMPARE_OP >
POP_JUMP_IF_FALSE else
LOAD_FAST a
LOAD_FAST b
BINARY_OP -
RETURN_VALUE
else:
LOAD_FAST b
LOAD_FAST a
BINARY_OP -
RETURN_VALUE
```スタック追跡:```text id="45f5d2"
LOAD_FAST a [a]
LOAD_FAST b [a, b]
COMPARE_OP > [a > b]
POP_JUMP... []
```どちらのブランチも直接戻るため、ブランチ後のマージはありません。
## 30.38 例: リストの内包表記
出典:```python id="k2d22v"
def f(xs):
return [x * 2 for x in xs if x > 0]
```概念的には、CPython は理解のためにネストされたコード オブジェクトを作成します。
外部関数:```text id="4gr87o"
load comprehension code object
make function
load xs
get iterator
call comprehension function
return result
```内包コード:```text id="c4xj0s"
build empty list
for each x in iterator:
if x > 0:
append x * 2
return list
```これは、内包表記に独自のスコープがある理由を説明します。
ネストされたコード オブジェクトがバイトコード命令ストリームによって表示されるため、これが可視になります。`co_consts`。
## 30.39 例: クロージャ
出典:```python id="9gbvx8"
def outer(x):
def inner(y):
return x + y
return inner
```重要なバイトコードの概念:```text id="5c33p4"
x becomes a cell variable in outer
x becomes a free variable in inner
outer creates inner with closure data
inner uses LOAD_DEREF to read x
```検査:```python id="x2ckc7"
def outer(x):
def inner(y):
return x + y
return inner
print(outer.__code__.co_cellvars)
inner = outer(10)
print(inner.__code__.co_freevars)
print(inner.__closure__)
```バイトコード命令はクロージャの構築と逆参照アクセスを示します。
## 30.40 例: 例外を試す
出典:```python id="g6xgtw"
def f(x):
try:
return 10 / x
except ZeroDivisionError:
return 0
```概念的な構造:```text id="eylcef"
protected region:
LOAD_CONST 10
LOAD_FAST x
BINARY_OP /
RETURN_VALUE
handler:
check exception matches ZeroDivisionError
LOAD_CONST 0
RETURN_VALUE
```コード オブジェクトには、どのバイトコード範囲が保護されるか、およびハンドラーがどこから始まるかを説明する例外テーブル メタデータが含まれています。
このバイトコードを読み取るときは、次の両方を検査してください。```text id="caeiew"
instruction stream
exception table
```例外テーブルは実行可能構造の一部です。
## 30.41 バイトコードとソース行
バイトコード命令はソース位置にマッピングされます。
このマッピングは以下をサポートします。```text id="vn48tw"
tracebacks
debuggers
coverage tools
profilers
line tracing
error messages
```単一のソース行をコンパイルして多くのバイトコード命令を生成できます。```python id="8bi6zl"
x = f(a) + g(b)
```概念的なバイトコードには次のものが含まれます。```text id="n0ncx0"
load f
load a
call f
load g
load b
call g
binary add
store x
```ソースの場所のメタデータにより、CPython はエラーおよびトレース イベントのより正確な位置をレポートできるようになります。
## 30.42 バイトコードとトレースバック
例外が発生すると、トレースバックはフレームと関連する命令/ソースの場所を記録します。
例:```python id="lzbl8z"
def f(x):
return 10 / x
f(0)
```失敗した操作は除算バイトコードです。 CPython は、フレームのコード オブジェクトと命令の位置を使用してソース行をレポートします。
したがって、トレースバックは次のものに接続されます。```text id="cc3baw"
frame
code object
instruction offset
source location table
exception state
```## 30.43 バイトコードと最適化
CPython は、コンパイル時および実行時にいくつかの最適化を実行します。
コンパイル時の例には次のものが含まれます。```text id="3ej829"
constant handling
dead code handling in simple cases
jump simplification
stack size computation
scope resolution
literal container optimizations
```実行時の例は次のとおりです。```text id="ta6h3c"
adaptive specialization
inline caches
optimized call paths
fast locals
specialized attribute access
specialized global lookup
```バイトコード命令ストリームは、コンパイラとランタイム オプティマイザの間に位置します。これはコンパイラの出力であり、インタープリタの入力でもあります。
## 30.44 バイトコードは安定した API ではありません
CPython バイトコードは、安定したパブリック仮想マシンのターゲットとして設計されていません。
以下をサポートするためにリリース間で変更される可能性があります。```text id="18txrq"
better performance
simpler interpreter implementation
new language features
better debugging information
new exception machinery
new call conventions
specialization
free-threading work
JIT experiments
```これは、バイトコードが使用できないという意味ではありません。つまり、バイトコード レベルのツールはバージョンを認識する必要があります。
プログラムの動作を安定させるには、Python 言語のセマンティクスに依存します。 CPython の内部動作については、正確な CPython バージョンのバイトコードを調べてください。
## 30.45 最小限のバイトコードインタープリター
おもちゃの通訳がアイデアを示すのに役立ちます。```python id="bo0wgp"
LOAD_CONST = "LOAD_CONST"
LOAD_FAST = "LOAD_FAST"
STORE_FAST = "STORE_FAST"
ADD = "ADD"
RETURN = "RETURN"
def run(code, consts, locals_):
stack = []
for op, arg in code:
if op == LOAD_CONST:
stack.append(consts[arg])
elif op == LOAD_FAST:
stack.append(locals_[arg])
elif op == STORE_FAST:
locals_[arg] = stack.pop()
elif op == ADD:
right = stack.pop()
left = stack.pop()
stack.append(left + right)
elif op == RETURN:
return stack.pop()
raise RuntimeError("missing RETURN")
```小さなプログラム:```python id="5gvw2y"
code = [
(LOAD_FAST, "a"),
(LOAD_FAST, "b"),
(ADD, None),
(STORE_FAST, "c"),
(LOAD_FAST, "c"),
(RETURN, None),
]
print(run(code, [], {"a": 2, "b": 3}))
```出力:```text id="n2zzxm"
5
```このおもちゃでは、CPython のほとんどが省略されています。```text id="mrtfdz"
objects
reference counts
exceptions
calls
descriptors
classes
imports
closures
generators
coroutines
specialization
inline caches
tracing
thread state
```しかし、これは核となる概念を捉えています。つまり、バイトコード命令はフレーム状態と値スタックで動作します。
## 30.46 よくある誤解
|誤解 |正しいモデル |
|---|---|
|バイトコードは別の構文の Python ソースです。バイトコードはインタプリタ命令ストリームです。
|バイトコードはすべての Python 実装間で移植可能です。 CPython バイトコードは CPython 固有です。
|バイトコードはバージョン間で安定しています。 CPython バージョン間のバイトコードの変更 |
|命令には完全な変数名が含まれています。多くの命令には、コード オブジェクト テーブルへのインデックスが含まれています。
|`dis`出力は完全なランタイムストーリーです。ランタイムはフレーム、キャッシュ、例外テーブル、特殊化も使用します。
| 1 つのソース行は 1 つの命令を意味します。多くの場合、1 行が多くの命令にコンパイルされます。
|バイトコードは常に構文に直接マッピングされます。ランタイム プロトコル機構用に一部のバイトコードが存在します。
|インライン キャッシュは Python 操作です。これらはインタープリタ最適化メタデータです。
## 30.47 読書戦略
バイトコード命令を理解するには、小さな例から作業してください。
以下から始めます:```python id="6w8kmn"
def f(a, b):
return a + b
```次に、以下を検査します。```python id="0tvkkr"
import dis
dis.dis(f)
```次に、次のことを確認します。```python id="6h63o9"
print(f.__code__.co_consts)
print(f.__code__.co_varnames)
print(f.__code__.co_names)
print(f.__code__.co_stacksize)
```それぞれの指示について、次のように尋ねます。```text id="w1yd94"
What does it consume from the stack?
What does it push?
Which code object table does it reference?
Can it jump?
Can it raise?
Can it call Python code?
Can specialization change its fast path?
```このメソッドは、単純な算術演算から関数、クロージャ、インポート、クラス、例外、および内包表記まで拡張します。
## 30.48 章の概要
バイトコード命令は、CPython の実行可能命令形式です。これらはコード オブジェクト内に存在し、評価ループを通じてフレームによって実行されます。
コアモデルは次のとおりです。```text id="rbfch8"
code object holds bytecode and metadata
frame holds execution state
bytecode instruction mutates frame state
evaluation loop dispatches instructions
stack effects define operand flow
```命令は、値の読み込み、値の保存、関数の呼び出し、操作の実行、コンテナーの構築、分岐、例外の処理、関数とクラスの作成、モジュールのインポート、ジェネレーターの一時停止、および結果の返しを行います。
バイトコードはコンパクト、動的、スタックベース、バージョン固有です。これを理解すると、Python ソースがどのように CPython 実行になるのかを直接理解できるようになります。