#4. Đọc mã CPython C

Đọc mã CPython C yêu cầu hai mô hình tinh thần cùng một lúc.

Mô hình đầu tiên là C thông thường: cấu trúc, con trỏ, macro, con trỏ hàm, quyền sở hữu tham chiếu, phân bổ, trả về lỗi và biên dịch có điều kiện.

Mô hình thứ hai là mô hình thời gian chạy của Python: đối tượng, loại, khung, ngoại lệ, số lượng tham chiếu, bộ mô tả, bộ lặp, mô-đun và mã byte.

Hầu hết các tệp nguồn CPython đều kết hợp cả hai. Một dòng mã C có thể trông giống như thao tác con trỏ thông thường, nhưng nó thường mã hóa quy tắc ngôn ngữ Python.

4.1 Bắt đầu từ bất biến thời gian chạy

Bất biến trung tâm rất đơn giản: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. Hầu hết các hàm CPython đều hoạt động với các giá trị thông quaPyObject *.

PyObject *obj;
```Con tr này có th tham chiếu đến mt s nguyên, danh sách, hàm, lp, mô-đun, ngoi l, chui hoc bt k đối tượng nào do người dùng định nghĩa.

Hành vi thc tế xut phát t loi đối tượng:```c id="y1r4zx"
Py_TYPE(obj)
```Kiu xác định thao tác nào hp l và hàm C nào thc hin chúng.

##4.2 Đọc`PyObject`đầu tiên

Tiêu đề đối tượng được đơn gin hóa trông như thế này:```c id="ogd09v"
typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;
```Các đối tượng có kích thước thay đổi m rng ý tưởng này:```c id="s6ik3g"
typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size;
} PyVarObject;
```Mt danh sách, b d liu, chui, đối tượng byte, dict, set và nhiu loi khác bt đầu bng tiêu đề đối tượng chung này. Điu đó cho phép CPython truyn gia các cu trúc đối tượng c th và`PyObject *`.

Hình dng ví d:```c id="05z3tr"
typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;
```vĩ mô`PyObject_VAR_HEAD`m rng đến tiêu đề đối tượng chung cng vi trường kích thước.

Đim quan trng là kh năng tương thích b cc. CPython có th vượt qua`PyListObject *`ti các API đối tượng chung bng cách truyn nó ti`PyObject *`.

## 4.3 Tìm hiểu các Macro cốt lõi

CPython s dng macro rt nhiu. Đừng b qua chúng. Nhiu hot động có v đơn gin s m rng thành hành vi quan trng.

Macro đối tượng ph biến:```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 */
```Macro kim tra loi:```c id="rkdbnt"
PyLong_Check(obj)
PyUnicode_Check(obj)
PyList_Check(obj)
PyTuple_Check(obj)
PyDict_Check(obj)
```Kim tra loi chính xác nhanh thường s dng các biến th như:```c id="houo83"
PyLong_CheckExact(obj)
PyUnicode_CheckExact(obj)
PyList_CheckExact(obj)
```S khác bit quan trng.`PyList_Check(obj)`chp nhn các lp con danh sách.`PyList_CheckExact(obj)`ch chp nhn danh sách tích hp chính xác.

##4.4 Hiểu quyền sở hữu tài liệu tham khảo

Quyn s hu tham chiếu là khó khăn ln đầu tiên trong mã CPython C.

Mt hàm tr về`PyObject *`có th tr li:

| Loi tham kho | Ý nghĩa |
| ------------------ | ---------------------------------------------- |
| Tài liu tham kho mi | Người gi s hu nó và cui cùng phi`Py_DECREF`|
| Tài liu tham kho mượn | Người gi không s hu nó |
| Tài liu tham kho b đánh cp | Callee nhn quyn s hu t người gi |

Điu này không th nhìn thy được t loi C. C tài liu tham kho mi và mượn đều ch là`PyObject *`.

Bn phi biết hp đồng API.

Ví d tài liu tham kho mi:```c id="4ox0zo"
PyObject *x = PyLong_FromLong(42);
/* use x */
Py_DECREF(x);

PyLong_FromLongtrả về một tham chiếu mới.

Ví dụ mượn tài liệu tham khảo:```c id="6j9ceg" PyObject item = PyList_GetItem(list, 0); / do not Py_DECREF(item) */


`PyList_GetItem`tr v mt tài liu tham kho mượn.

Ví d tham chiếu mnh m t các API mi hơn:```c id="kqi42o"
PyObject *item = PySequence_GetItem(seq, 0);
/* must Py_DECREF(item) */
Py_DECREF(item);

PySequence_GetItemtrả về một tham chiếu mới.

Một người đọc đúng sẽ yêu cầu mọiPyObject *:

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 Nhận biết lỗi trả về

API CPython C thường báo hiệu lỗi bằng cách trả về các giá trị trọng điểm và đặt ngoại lệ.

Các mẫu phổ biến:

| Kiểu trả về | Giá trị lỗi |
| ----------------- | -------------------------------- |
|`PyObject *`      | `NULL`                           |
| `int`             | `-1`                             |
| `Py_ssize_t`      | `-1`, thường có kiểm tra ngoại lệ |
| con trỏ |`NULL`|
| kết quả so sánh |`-1`có thể có nghĩa là lỗi |

Ví dụ:```c id="3udr6f"
PyObject *value = PyObject_GetAttrString(obj, "name");
if (value == NULL) {
    return NULL;
}
```Ngoại lệ đã được thiết lập. Người gọi thường truyền bá nó bằng cách quay lại`NULL`.

Đối với các API giống số nguyên,`-1`có thể mơ hồ. Một số API yêu cầu kiểm tra xem có ngoại lệ xảy ra hay không:```c id="29oa6e"
Py_ssize_t n = PyLong_AsSsize_t(obj);
if (n == -1 && PyErr_Occurred()) {
    return NULL;
}
```Mô hình này phổ biến vì`-1`cũng có thể là giá trị Python hợp lệ.

## 4.6 Theo dõi trạng thái ngoại lệ

Các ngoại lệ CPython được lưu trữ ở trạng thái luồng thời gian chạy, thường không được trả về dưới dạng giá trị C.

Khi mã Python này tăng lên:```python id="0j7gku"
raise ValueError("bad value")
```CPython ghi lại một ngoại lệ đang hoạt động. Sau đó, các hàm C sẽ truyền lỗi bằng cách trả về các thông báo lỗi.

Mẫu C điển hình:```c id="y9ubjz"
if (bad_condition) {
    PyErr_SetString(PyExc_ValueError, "bad value");
    return NULL;
}
```Sau đó, người gọi sẽ thực hiện:```c id="356gdj"
result = some_function();
if (result == NULL) {
    return NULL;
}
```Không có đối tượng ngoại lệ rõ ràng nào được chuyển qua ngăn xếp lệnh gọi C trong hầu hết các trường hợp. Ngoại lệ được lưu trữ ở trạng thái thông dịch viên, trong khi`NULL`hoặc`-1`mang dòng điều khiển.

Điều này giải thích tại sao việc không kiểm tra giá trị trả về có thể làm hỏng quá trình thực thi sau này.

## 4.7 Đọc kỹ đường dẫn dọn dẹp

Hầu hết các hàm CPython C không cần thiết đều có nhiều lần thoát lỗi.

Một mô hình phổ biến:```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;
```Loại mã này không phải là ngẫu nhiên. Nó mã hóa quyền sở hữu tham chiếu.

Khi đọc mã dọn dẹp, hãy xác minh:```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.
```Nhiều lỗi CPython là lỗi đếm tham chiếu trong các đường dẫn lỗi không phổ biến.

## 4.8 Biết khi nào mã C có thể chạy mã Python

Lệnh gọi hàm C có thể thực thi mã Python tùy ý.

Ví dụ bao gồm:```text id="s31qgj"
attribute access
method calls
comparisons
hashing
iteration
descriptor invocation
numeric operations
imports
finalizers
weakref callbacks
```Điều này quan trọng vì mã Python tùy ý có thể biến đổi các đối tượng, giải phóng các tham chiếu, nhập lại trình thông dịch, kích hoạt thu thập rác hoặc đưa ra các ngoại lệ.

Ví dụ:```c id="u11u69"
int equal = PyObject_RichCompareBool(a, b, Py_EQ);
```Điều này có thể gọi do người dùng xác định`__eq__`.

Tương tự:```c id="kfptiw"
Py_hash_t h = PyObject_Hash(obj);
```Điều này có thể gọi do người dùng xác định`__hash__`.

Khi đọc mã CPython C, đừng bao giờ cho rằng một đối tượng không thay đổi trong suốt cuộc gọi có thể thực thi mã Python trừ khi mã sở hữu các tham chiếu phù hợp và đã bảo vệ các bất biến của nó.

## 4.9 Đọc các đối tượng loại dưới dạng bảng điều phối

A`PyTypeObject`mô tả cách một loại hoạt động.

Ý tưởng đơn giản hóa:```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,
};
```Một đối tượng kiểu chứa các vị trí cho:```text id="jf802y"
allocation
deallocation
attribute access
call behavior
numeric operations
sequence operations
mapping operations
iteration
methods
members
getters and setters
subclass behavior
```Cú pháp Python thường ánh xạ tới các vị trí này.

| Hoạt động Python | Tuyến nội bộ |
| ---------------- | ---------------------------------- |
|`len(x)`| khe độ dài trình tự hoặc ánh xạ |
|`x[y]`| vị trí đăng ký ánh xạ hoặc trình tự |
|`x + y`| khe thêm số |
|`x()`| khe gọi |
|`iter(x)`| khe lặp |
|`x.y`| khe truy cập thuộc tính |
|`repr(x)`| khe đại diện |

Vì vậy, khi đọc một loại có sẵn, trước tiên hãy tìm nó`PyTypeObject`. Nó đóng vai trò như mục lục để thực hiện.

## 4.10 Phân biệt API chung và API cụ thể theo loại

CPython thường có cả API đối tượng chung và API loại chính xác.

API chung:```c id="e77tm2"
PyObject_GetItem(obj, key)
PyObject_SetAttr(obj, name, value)
PyObject_Call(func, args, kwargs)
PyObject_RichCompare(a, b, Py_EQ)
```Những điều này tôn trọng khả năng tùy chỉnh ở cấp độ Python. Họ có thể gọi mã người dùng.

API dành riêng cho loại:```c id="3m2zu8"
PyList_GET_ITEM(list, i)
PyTuple_GET_ITEM(tuple, i)
PyDict_GetItemWithError(dict, key)
```Chúng thường giả định các loại chính xác và có thể bỏ qua công văn cấp Python.

Các macro nhanh như`PyList_GET_ITEM`có thể không an toàn nếu sử dụng sai loại hoặc chỉ mục không hợp lệ. Họ nhanh chóng vì họ bỏ qua việc kiểm tra.

Khi đọc mã, hãy hỏi xem hàm có cần ngữ nghĩa Python hay tốc độ bên trong hay không.

## 4.11 Hiểu con trỏ mượn vào vùng chứa

Một số API hiển thị các tham chiếu mượn đến các đối tượng được lưu trữ bên trong vùng chứa.

Ví dụ:```c id="9tw6vq"
PyObject *item = PyList_GetItem(list, i);
```Sự trở lại`item`chỉ hợp lệ khi danh sách vẫn giữ tham chiếu đó.

Nếu sau này mã cho phép thực thi Python thì danh sách có thể thay đổi và giải phóng mục đó. Mã an toàn thường tăng tham chiếu trước các lệnh gọi như vậy:```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);
```Mô hình này là cơ bản. Các tài liệu tham khảo mượn có hiệu quả nhưng chúng đòi hỏi phải có lý luận chặt chẽ suốt đời.

## 4.12 Đọc mã phân tích đối số

Các hàm C tiếp xúc với Python thường phân tích cú pháp các đối số bằng API trợ giúp hoặc mã được tạo bởi Argument Clinic.

Phong cách thủ công:```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);
}
```Kiểu từ khóa:```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;
}
```Kiểu Phòng khám đối số tạo ra phần lớn mã bao bọc này. Khi bạn thấy các khối được tạo, hãy xác định logic viết tay và tách nó khỏi bản mẫu phân tích cú pháp được tạo.

## 4.13 Biết Người Trợ Giúp Hoàn Trả Chung

CPython sử dụng macro trợ giúp cho các giá trị trả về chung.```c id="83y546"
Py_RETURN_NONE;
Py_RETURN_TRUE;
Py_RETURN_FALSE;
```Chúng tăng tham chiếu singleton và trả về nó.

Ý tưởng tương đương:```c id="0iy1et"
Py_INCREF(Py_None);
return Py_None;
```Mã mới hơn có thể sử dụng trình trợ giúp tham chiếu mạnh và API tiện lợi nội bộ. Quy tắc vẫn giữ nguyên: các đối tượng được trả về thường cần phải thuộc sở hữu của người gọi.

## 4.14 Đọc hàm Deallocation chậm

Mỗi loại đối tượng đều có một đường dẫn phân bổ.

Hình dạng ví dụ:```c id="tr2vxz"
static void
type_dealloc(MyObject *self)
{
    Py_XDECREF(self->field);
    Py_TYPE(self)->tp_free((PyObject *)self);
}
```Deallocation phải giải phóng các tài liệu tham khảo thuộc sở hữu và bộ nhớ trống. Nhưng việc phân bổ có thể tinh tế bởi vì`Py_DECREF`có thể thực hiện nhiều sự phân bổ hơn, điều này có thể kích hoạt các lệnh hoàn thiện hoặc các lệnh gọi lại yếu.

Đối với các đối tượng vùng chứa, việc phân bổ thường xóa các tham chiếu được chứa một cách cẩn thận.

Các câu hỏi quan trọng:```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?
```Lỗi phân bổ thường xuất hiện dưới dạng rò rỉ, treo máy, đối tượng được phục hồi hoặc truy cập bộ nhớ không hợp lệ.

## 4.15 Nhận dạng mã giao thức thu gom rác

Các loại vùng chứa có thể tham gia vào chu trình sẽ triển khai hỗ trợ GC.

Bạn có thể thấy các chức năng như:```c id="wm6ch5"
tp_traverse
tp_clear
PyObject_GC_Track
PyObject_GC_UnTrack
PyObject_GC_Del
```Các lượt truy cập hàm duyệt có chứa các tham chiếu:```c id="zo14wa"
static int
my_traverse(MyObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->field);
    return 0;
}
```Hàm clear giải phóng các tham chiếu có thể tạo thành chu trình:```c id="l0u4gi"
static int
my_clear(MyObject *self)
{
    Py_CLEAR(self->field);
    return 0;
}

Py_CLEARđặt trường thànhNULLtrước khi giảm tham chiếu. Điều này ngăn không cho mã đăng ký lại nhìn thấy con trỏ lơ lửng.

Mã hỗ trợ GC trông có vẻ máy móc nhưng lại cần thiết để đảm bảo tính chính xác.

4.16 Đọc khi mở tệp thử nghiệm

Đừng đọc các tập tin thực hiện một mình.

Objects/listobject.c, giữLib/test/test_list.pygần đó.

Objects/dictobject.c, giữLib/test/test_dict.pygần đó.

Đối với mô tả và các lớp, sử dụngLib/test/test_descr.py.

Đối với hành vi của trình biên dịch, hãy sử dụngLib/test/test_compile.py, Lib/test/test_ast.py, VàLib/test/test_dis.py.

Các thử nghiệm cho thấy hành vi dự kiến, các trường hợp đặc biệt và các trường hợp hồi quy lịch sử.

Một vòng lặp đọc hiệu quả:```text id="2ghmn1" find Python feature find test file run targeted test read implementation modify small behavior or add print rebuild run test again


Các mu tìm kiếm hu ích:```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
```S dng`git grep`bên trong kho lưu tr:```bash id="4als46"
git grep "PyDict_GetItem"
git grep "tp_dealloc"
git grep "PyObject_RichCompareBool"
git grep "Argument Clinic"
```Trước tiên hãy tìm kiếm đối tượng loi, sau đó đi theo các v trí đến hàm.

## 4.18 Ví dụ đọc thực tế:`list.append`Bắt đầu từ Python:```python id="6wptq9"
xs = []
xs.append(1)
```Tìm bng phương pháp trong`Objects/listobject.c`.

Nó s cha mt mc phương thc cho`append`.

V mt khái nim:```c id="rbqcp6"
{"append", list_append, METH_O, "..."}
```Sau đó đọc phn thc hin.

Hình dng đơn gin:```c id="06amht"
static PyObject *
list_append(PyListObject *self, PyObject *object)
{
    if (_PyList_AppendTakeRef(self, Py_NewRef(object)) < 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}
```Nhng đim quan trng là:```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
```Sau đó làm theo người tr giúp để thay đổi kích thước danh sách nếu cn.

Con đường đó dy:```text id="bc2gxm"
method tables
argument calling convention
list over-allocation
reference ownership
error handling
return helpers
```Mt phương pháp nh có th hin th mt s thành ng CPython.

## 4.19 Ví dụ đọc thực tế:`dict[key]`Bắt đầu từ Python:```python id="ghl7wv"
value = d[key]
```Thao tác này ánh x ti hành vi ch s t đin.

Đường đọc:```text id="agveol"
Objects/dictobject.c
    
dict type object
    
mapping methods
    
subscript function
    
hash lookup path
```Các câu hi quan trng:```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?
```Mã t đin rt quan trng v hiu sut và được ti ưu hóa cao. Đọc nó theo tng lp: hành vi công khai trước tiên, th hai là tra cu người tr giúp, th ba là b cc bng.

## 4.20 Phong cách của CPython C

Mã CPython C có xu hướng ưu tiên lung điu khin rõ ràng hơn là tru tượng hóa.

Đặc đim chung:```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
```Phong cách này là thc tế. CPython là mã C cũ, di động, nhy cm vi hiu sut vi các yêu cu tương thích nghiêm ngt.

Đừng mong đợi mt kiến ​​trúc C nh gn, thun túy hin đại. Mong đợi s tiến hóa theo lp.

##4.21 Những lỗi thường gặp khi đọc CPython

| Sai lm | Sa cha |
| ---------------------------------------------- | -------------------------------------------- |
| điu tr`PyObject *`như mt loi bê tông | Nó là mt con tr chung ti bt k đối tượng Python nào |
| B qua quyn s hu tài liu tham kho | Mi con tr đối tượng đều có quy tc s hu |
| Gi s`NULL`nghĩa là không có giá tr | Nó thường có nghĩa là ngoi l |
| Gi s cuc gi C không th chy Python | Nhiu API đối tượng có th chy mã Python |
| Chnh sa mã được to | Chnh sa đầu vào ngun và to li |
| Đọc macro nhanh dưới dng API an toàn | Nhiu người b qua kim tra |
| Gi s mã byte n định | Thay đổi mã byte gia các phiên bn |
| Gi s hành vi CPython là hành vi ngôn ng | Mt s hành vi được trin khai c th |

## 4.22 Danh sách kiểm tra tối thiểu cho mọi chức năng

Khi đọc hàm CPython C, hãy tr li nhng câu hi sau:```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?
```Danh sách kim tra này ngăn nga hu hết các trường hp đọc sai.

## 4.23 Tóm tắt chương

Mã CPython C có th đọc được khi bn theo dõi ba điu mt cách nht quán: b cc đối tượng, quyn s hu tham chiếu và lan truyn li. Hu hết các giá tr thi gian chy được x lý như`PyObject *`. Đối tượng loi xác định hành vi thông qua các v trí. Các hàm báo hiu li thông qua các trng đim tr v và trng thái ngoi l ca trình thông dch. Vic đếm tham chiếu giúp hin th quyn s hu trong mi dòng mã.

Đọc mã CPython khi m các bài kim tra, theo dõi các đối tượng loi đến các v trí, coi macro là mã thc và gi định rng nhiu thao tác đối tượng chung có th thc thi mã Python tùy ý.