4. CPython C コードの読み取り

CPython C コードを読み取るには、同時に 2 つのメンタル モデルが必要です。

最初のモデルは通常の C: 構造体、ポインター、マクロ、関数ポインター、参照の所有権、割り当て、エラー戻り値、および条件付きコンパイルです。

2 番目のモデルは Python のランタイム モデルです。オブジェクト、型、フレーム、例外、参照カウント、記述子、反復子、モジュール、バイトコードです。

ほとんどの CPython ソース ファイルは両方を組み合わせています。 C コードの行は通常のポインター操作のように見えるかもしれませんが、多くの場合、Python 言語ルールがエンコードされています。

4.1 実行時不変式から開始する

中心となる不変式は単純です。text id="vm1twr" Every Python value is represented as a PyObject pointer or a pointer to a struct whose first field is compatible with PyObject. ほとんどの CPython 関数は値を処理します。PyObject *。```c id="x34zg4" PyObject *obj;


実際の動作はオブジェクトのタイプから決まります。```c id="y1r4zx"
Py_TYPE(obj)
```型によって、どの操作が有効か、どの C 関数がそれらの操作を実装するかが決まります。

## 4.2 読む`PyObject`まず

簡略化されたオブジェクト ヘッダーは次のようになります。```c id="ogd09v"
typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;
```可変サイズのオブジェクトは、この考え方を拡張します。```c id="s6ik3g"
typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size;
} PyVarObject;
```リスト、タプル、文字列、バイト オブジェクト、dictset、およびその他の多くの型は、この共通のオブジェクト ヘッダーで始まります。これにより、CPython は特定のオブジェクト構造体とオブジェクトの間でキャストできるようになります。`PyObject *`

形状の例:```c id="05z3tr"
typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;
```マクロ`PyObject_VAR_HEAD`共通オブジェクトヘッダーとサイズフィールドに展開されます。

重要な点はレイアウトの互換性です。 CPython は、`PyListObject *`にキャストして汎用オブジェクト API に変換します。`PyObject *`

## 4.3 コアマクロを学ぶ

CPython はマクロを多用します。スキップしないでください。単純に見える操作の多くは、重要な動作に拡張されます。

一般的なオブジェクト マクロ:```c id="0tneoh"
Py_TYPE(obj)       /* get object type */
Py_SIZE(obj)       /* get variable object size */
Py_REFCNT(obj)     /* get reference count */
Py_INCREF(obj)     /* increment reference count */
Py_DECREF(obj)     /* decrement reference count */
Py_XINCREF(obj)    /* increment if not NULL */
Py_XDECREF(obj)    /* decrement if not NULL */
```型チェックマクロ:```c id="rkdbnt"
PyLong_Check(obj)
PyUnicode_Check(obj)
PyList_Check(obj)
PyTuple_Check(obj)
PyDict_Check(obj)
```高速の正確な型チェックでは、次のようなバリアントがよく使用されます。```c id="houo83"
PyLong_CheckExact(obj)
PyUnicode_CheckExact(obj)
PyList_CheckExact(obj)
```その違いが重要なのです。`PyList_Check(obj)`リストのサブクラスを受け入れます。`PyList_CheckExact(obj)`正確な組み込みリストのみを受け入れます。

## 4.4 参照の所有権を理解する

参照の所有権は、CPython C コードの最初の大きな問題です。

関数が返す`PyObject *`返される可能性があります:

|参照の種類 |意味 |
| ------------------ | ------------------------------------------------ |
|新しいリファレンス |呼び出し元がそれを所有しており、最終的にはそうする必要があります`Py_DECREF`|
|お借りした参考資料 |呼び出し元はそれを所有していません。
|盗まれたリファレンス |呼び出し先が呼び出し元から所有権を取得 |

これはCタイプからは見えません。新しい参考文献も借りた参考文献も、`PyObject *`

API コントラクトについて知っておく必要があります。

新しい参照の例:```c id="4ox0zo"
PyObject *x = PyLong_FromLong(42);
/* use x */
Py_DECREF(x);

PyLong_FromLong新しい参照を返します。

借用したリファレンスの例:```c id="6j9ceg" PyObject item = PyList_GetItem(list, 0); / do not Py_DECREF(item) */


`PyList_GetItem`借用した参照を返します。

新しい API からの強力な参照の例:```c id="kqi42o"
PyObject *item = PySequence_GetItem(seq, 0);
/* must Py_DECREF(item) */
Py_DECREF(item);

PySequence_GetItem新しい参照を返します。

正しい読者はあらゆることを求めますPyObject *:

Who owns this reference?
Who must release it?
Can this pointer be NULL?
Can this call execute Python code?
Can this call mutate the container?
```## 4.5 エラー戻り値を認識する

CPython C API は通常、センチネル値を返し、例外を設定することによってエラーを通知します。

よくあるパターン:

|戻り値の型 |エラー値 |
| ----------------- | -------------------------------- |
|`PyObject *`      | `NULL`                           |
| `int`             | `-1`                             |
| `Py_ssize_t`      | `-1`、多くの場合、例外チェックが行われます。
|ポインタ |`NULL`|
|比較結果 |`-1`エラーを意味する可能性があります |

例:```c id="3udr6f"
PyObject *value = PyObject_GetAttrString(obj, "name");
if (value == NULL) {
    return NULL;
}
```例外はすでに設定されています。呼び出し元は通常、次のコマンドを返すことによってそれを伝播します。`NULL`。

整数のような API の場合、`-1`曖昧になる可能性があります。一部の API では、例外が発生したかどうかを確認する必要があります。```c id="29oa6e"
Py_ssize_t n = PyLong_AsSsize_t(obj);
if (n == -1 && PyErr_Occurred()) {
    return NULL;
}
```このパターンはよくあることなので、`-1`有効な Python 値である場合もあります。

## 4.6 例外状態のトレース

CPython 例外は実行時のスレッド状態に保存され、通常は C 値として返されません。

この Python コードが発生すると、次のようになります。```python id="0j7gku"
raise ValueError("bad value")
```CPython はアクティブな例外を記録します。その後、C 関数はエラー センチネルを返すことによって失敗を伝播します。

典型的な C パターン:```c id="y9ubjz"
if (bad_condition) {
    PyErr_SetString(PyExc_ValueError, "bad value");
    return NULL;
}
```次に、呼び出し側は次のことを行います。```c id="356gdj"
result = some_function();
if (result == NULL) {
    return NULL;
}
```ほとんどの場合、C 呼び出しスタックを介して明示的な例外オブジェクトが渡されることはありません。例外はインタープリタ状態に保存されますが、`NULL`または`-1`制御フローを伝送します。

これは、戻り値のチェックに失敗すると、後の実行が破損する可能性がある理由を説明しています。

## 4.7 クリーンアップ パスを注意深く読む

自明ではない CPython C 関数のほとんどには、複数の失敗終了があります。

よくあるパターン:```c id="u4ryic"
PyObject *a = NULL;
PyObject *b = NULL;
PyObject *result = NULL;

a = make_a();
if (a == NULL) {
    goto error;
}

b = make_b();
if (b == NULL) {
    goto error;
}

result = combine(a, b);

error:
Py_XDECREF(a);
Py_XDECREF(b);
return result;
```この種のコードは偶然ではありません。参照の所有権をエンコードします。

クリーンアップ コードを読み取るときは、次のことを確認してください。```text id="73pxzg"
Every owned reference is released exactly once.
Borrowed references are not decref'd.
Objects are still valid when used.
Error paths preserve the active exception.
Success paths return the correct ownership.
```CPython のバグの多くは、まれな障害パスにおける参照カウントのバグです。

## 4.8 C コードが Python コードを実行できる時期を知る

C 関数呼び出しは、任意の Python コードを実行する可能性があります。

例としては次のものが挙げられます。```text id="s31qgj"
attribute access
method calls
comparisons
hashing
iteration
descriptor invocation
numeric operations
imports
finalizers
weakref callbacks
```これは、任意の Python コードがオブジェクトを変更したり、参照を解放したり、インタープリタを再入力したり、ガベージ コレクションをトリガーしたり、例外を発生させたりする可能性があるため、重要です。

例えば:```c id="u11u69"
int equal = PyObject_RichCompareBool(a, b, Py_EQ);
```これはユーザー定義を呼び出す可能性があります`__eq__`。

同じく:```c id="kfptiw"
Py_hash_t h = PyObject_Hash(obj);
```これはユーザー定義を呼び出す可能性があります`__hash__`。

CPython C コードを読むときは、コードが正しい参照を所有し、その不変条件を保護していない限り、Python コードを実行できる呼び出し全体でオブジェクトが変更されないままであると想定しないでください。

## 4.9 型オブジェクトをディスパッチテーブルとして読み取る

あ`PyTypeObject`型がどのように動作するかを説明します。

単純化したアイデア:```c id="48r26q"
PyTypeObject PyList_Type = {
    .tp_name = "list",
    .tp_basicsize = sizeof(PyListObject),
    .tp_dealloc = list_dealloc,
    .tp_repr = list_repr,
    .tp_as_sequence = &list_as_sequence,
    .tp_methods = list_methods,
    .tp_new = list_new,
};
```タイプ オブジェクトには、次のスロットが含まれます。```text id="jf802y"
allocation
deallocation
attribute access
call behavior
numeric operations
sequence operations
mapping operations
iteration
methods
members
getters and setters
subclass behavior
```Python 構文は多くの場合、これらのスロットにマップされます。

| Python の操作 |内部ルート |
| ---------------- | ---------------------------------- |
|`len(x)`|シーケンスまたはマッピングの長さスロット |
|`x[y]`|マッピングまたはシーケンス添え字スロット |
|`x + y`|数値追加スロット |
|`x()`|コールスロット |
|`iter(x)`|イテレータスロット |
|`x.y`|属性アクセススロット |
|`repr(x)`|再現スロット |

したがって、組み込み型を読み取るときは、まずその型を見つけます。`PyTypeObject`。これは実装の目次として機能します。

## 4.10 汎用 API と型固有 API を区別する

CPython には、多くの場合、汎用オブジェクト API と正確な型 API の両方があります。

汎用 API:```c id="e77tm2"
PyObject_GetItem(obj, key)
PyObject_SetAttr(obj, name, value)
PyObject_Call(func, args, kwargs)
PyObject_RichCompare(a, b, Py_EQ)
```これらは Python レベルのカスタマイズを尊重します。ユーザーコードを呼び出す可能性があります。

タイプ固有の API:```c id="3m2zu8"
PyList_GET_ITEM(list, i)
PyTuple_GET_ITEM(tuple, i)
PyDict_GetItemWithError(dict, key)
```これらは多くの場合、正確な型を想定しており、Python レベルのディスパッチをバイパスする可能性があります。

次のような高速マクロ`PyList_GET_ITEM`間違ったタイプまたは無効なインデックスとともに使用すると、安全でない可能性があります。チェックをスキップするので速いです。

コードを読むときは、関数に Python のセマンティクスが必要か、それとも内部速度が必要かどうかを確認してください。

## 4.11 Understand Borrowed Pointers Into Containers

一部の API は、コンテナ内に格納されているオブジェクトへの借用された参照を公開します。

例:```c id="9tw6vq"
PyObject *item = PyList_GetItem(list, i);
```返された`item`は、リストがその参照を維持している間のみ有効です。

後でコードで Python の実行が許可されると、リストが変化して項目が解放される可能性があります。安全なコードは、多くの場合、そのような呼び出しの前に参照をインクリメントします。```c id="0wl8te"
PyObject *item = PyList_GetItem(list, i);  /* borrowed */
if (item == NULL) {
    return NULL;
}

Py_INCREF(item);
/* safe across calls that may mutate list */
...
Py_DECREF(item);
```このパターンが基本です。借用参照は効率的ですが、厳密な存続期間推論が必要です。

## 4.12 引数解析コードの読み取り

Python に公開される C 関数は通常、ヘルパー API または Argument Clinic で生成されたコードを使用して引数を解析します。

マニュアルスタイル:```c id="0jk1n5"
static PyObject *
mod_func(PyObject *self, PyObject *args)
{
    int n;

    if (!PyArg_ParseTuple(args, "i", &n)) {
        return NULL;
    }

    return PyLong_FromLong(n + 1);
}
```キーワードのスタイル:```c id="6r9q8i"
static PyObject *
mod_func(PyObject *self, PyObject *args, PyObject *kwargs)
{
    static char *kwlist[] = {"name", NULL};
    const char *name;

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) {
        return NULL;
    }

    Py_RETURN_NONE;
}
```Argument Clinic スタイルは、このラッパー コードの多くを生成します。生成されたブロックが表示されたら、手書きのロジックを特定し、生成された解析ボイラープレートから分離します。

## 4.13 一般的なリターンヘルパーを知る

CPython は、一般的な戻り値にヘルパー マクロを使用します。```c id="83y546"
Py_RETURN_NONE;
Py_RETURN_TRUE;
Py_RETURN_FALSE;
```これらはシングルトン参照をインクリメントして返します。

同等のアイデア:```c id="0iy1et"
Py_INCREF(Py_None);
return Py_None;
```新しいコードでは、強参照ヘルパーと内部便利な API を使用する場合があります。ルールは変わりません。返されるオブジェクトは通常、呼び出し元が所有する必要があります。

## 4.14 割り当て解除関数をゆっくり読み取る

すべてのオブジェクト タイプには割り当て解除パスがあります。

形状の例:```c id="tr2vxz"
static void
type_dealloc(MyObject *self)
{
    Py_XDECREF(self->field);
    Py_TYPE(self)->tp_free((PyObject *)self);
}
```割り当てを解除するには、所有している参照を解放し、メモリを解放する必要があります。ただし、割り当て解除は微妙な場合があります。`Py_DECREF`より多くの割り当て解除を実行でき、これによりファイナライザーまたはweakrefコールバックがトリガーされる可能性があります。

コンテナ オブジェクトの場合、割り当て解除では、含まれる参照が慎重にクリアされることがよくあります。

重要な質問:```text id="va1g1h"
Does this object participate in cyclic GC?
Does it need to untrack itself before clearing fields?
Can clearing a field run Python code?
Does it support weakrefs?
Does it have a finalizer?
Which allocator frees the memory?
```割り当て解除のバグは、リーク、クラッシュ、オブジェクトの復活、または無効なメモリ アクセスとして現れることがよくあります。

## 4.15 ガベージ コレクターのプロトコル コードを認識する

サイクルに参加できるコンテナー タイプは GC サポートを実装します。

次のような関数が表示される場合があります。```c id="wm6ch5"
tp_traverse
tp_clear
PyObject_GC_Track
PyObject_GC_UnTrack
PyObject_GC_Del
```トラバース関数の訪問には、次の参照が含まれていました。```c id="zo14wa"
static int
my_traverse(MyObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->field);
    return 0;
}
```clear 関数は、サイクルを形成する可能性のある参照を解放します。```c id="l0u4gi"
static int
my_clear(MyObject *self)
{
    Py_CLEAR(self->field);
    return 0;
}

Py_CLEARフィールドを次のように設定しますNULL参照をデクリメントする前に。これにより、再入可能なコードがダングリング ポインターを認識するのを防ぎます。

GC サポート コードは機械的に見えますが、正確さを保つためには不可欠です。

4.16 テストファイルを開いた状態で読み取る

実装ファイルだけを読まないでください。

のためにObjects/listobject.c、 保つLib/test/test_list.py近く。

のためにObjects/dictobject.c、 保つLib/test/test_dict.py近く。

記述子とクラスには、次を使用します。Lib/test/test_descr.py

コンパイラの動作については、次を使用します。Lib/test/test_compile.pyLib/test/test_ast.py、 そしてLib/test/test_dis.py

テストは、意図された動作、エッジケース、および過去の回帰ケースを示します。

生産的な読書ループ:```text id="2ghmn1" find Python feature find test file run targeted test read implementation modify small behavior or add print rebuild run test again


便利な検索パターン:```bash id="7h5kd5"
grep -R "PyList_Type" Objects Include Python Modules
grep -R "list_append" Objects
grep -R "PyArg_ParseTuple" Modules Objects Python
grep -R "tp_as_mapping" Objects
grep -R "PyErr_SetString" Objects Python Modules
```使用`git grep`リポジトリ内:```bash id="4als46"
git grep "PyDict_GetItem"
git grep "tp_dealloc"
git grep "PyObject_RichCompareBool"
git grep "Argument Clinic"
```最初に型オブジェクトを検索し、次にスロットをたどって関数を見つけます。

## 4.18 実際の読み取り例:`list.append`Python から始めます:```python id="6wptq9"
xs = []
xs.append(1)
```次のメソッド テーブルを見つけます。`Objects/listobject.c`

これには、次のメソッドエントリが含まれます。`append`

概念的には:```c id="rbqcp6"
{"append", list_append, METH_O, "..."}
```次に、実装を読みます。

簡略化された形状:```c id="06amht"
static PyObject *
list_append(PyListObject *self, PyObject *object)
{
    if (_PyList_AppendTakeRef(self, Py_NewRef(object)) < 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}
```重要な点は次のとおりです。```text id="ud5hhf"
self is the list object
object is the item passed from Python
the list stores a new reference to object
failure returns NULL with an exception set
success returns None
```次に、必要に応じてリストのサイズを変更するヘルパーに従います。

その道は次のことを教えてくれます。```text id="bc2gxm"
method tables
argument calling convention
list over-allocation
reference ownership
error handling
return helpers
```1 つの小さなメソッドで複数の CPython イディオムを公開できます。

## 4.19 実際の読み取り例:`dict[key]`Python から始めます:```python id="ghl7wv"
value = d[key]
```この操作は、辞書の添字の動作にマップされます。

読み取りパス:```text id="agveol"
Objects/dictobject.c
    
dict type object
    
mapping methods
    
subscript function
    
hash lookup path
```重要な質問:```text id="iws5gu"
Is the key hashable?
Does hashing call Python code?
How are missing keys handled?
How are exceptions distinguished from absence?
Does this path return a borrowed or new reference?
```辞書コードはパフォーマンスが重要であり、高度に最適化されています。階層的に読んでください。最初にパブリック動作、2番目にルックアップヘルパー、3番目にテーブルレイアウトです。

## 4.20 CPython C のスタイル

CPython C コードは、抽象化よりも明示的な制御フローを好む傾向があります。

共通の特徴:```text id="k252pu"
manual reference counting
explicit error checks
goto-based cleanup
macros for hot paths
function pointers through type slots
separate fast paths and generic paths
conditional compilation for platforms
generated wrappers for Python-callable functions
```このスタイルは実用的です。 CPython は古い、移植可能で、パフォーマンスに敏感な C コードであり、厳密な互換性要件があります。

小規模で純粋に最新の C アーキテクチャを期待しないでください。多層的な進化が期待できます。

## 4.21 CPython を読むときによくある間違い

|間違い |訂正 |
| ------------------------------------------------ | -------------------------------------------- |
|治療中`PyObject *`具体的なタイプとして |これは任意の Python オブジェクトへの汎用ポインターです。
|参照の所有権を無視する |すべてのオブジェクト ポインターには所有権ルールがあります。
|仮定すると`NULL`| 値がないことを意味します通常、例外 | を意味します。
| C 呼び出しでは Python を実行できないと仮定します。多くのオブジェクト API  Python コードを実行できます。
|生成されたコードの編集 |ソース入力を編集して再生成 |
|安全な API として高速マクロを読み取る |スキップチェックが多い |
|バイトコードが安定していると仮定します。バージョン間のバイトコードの変更 |
| CPython の動作が言語の動作であると仮定します。一部の動作は実装に固有です。

## 4.22 あらゆる関数の最小限のチェックリスト

CPython C 関数を読むときは、次の質問に答えてください。```text id="ztg0lo"
What Python behavior does this implement?
What are the input reference ownership rules?
What does the function return on success?
What does it return on failure?
Does it set or propagate an exception?
Which references does it own?
Which references are borrowed?
Can any call execute Python code?
Can any object be mutated during the function?
Are there cleanup paths?
Is this public API, private API, or internal helper?
Is any part generated?
Which tests cover it?
```このチェックリストは、ほとんどの読み間違いを防ぎます。

## 4.23 章の概要

CPython C コードは、オブジェクト レイアウト、参照の所有権、エラーの伝播という 3 つのことを一貫して追跡すると、読み取れるようになります。ほとんどの実行時の値は次のように処理されます。`PyObject *`。タイプ オブジェクトは、スロットを通じて動作を定義します。関数は、リターン センチネルとインタープリタ例外状態を通じてエラーを通知します。参照カウントにより、コードのすべての行で所有権が可視化されます。

テストを開いた状態で CPython コードを読み取り、型オブジェクトをスロットまで追跡し、マクロを実際のコードとして扱い、多くの汎用オブジェクト操作で任意の Python コードを実行できると想定します。