8. PyObject và PyVarObject
8.PyObjectVàPyVarObject
PyObjectVàPyVarObjectlà các bố cục cơ sở đằng sau các đối tượng CPython. Chúng không phải là lớp Python. Chúng là các quy ước cấu trúc cấp C cho phép bộ thực thi xử lý nhiều triển khai đối tượng khác nhau thông qua một kiểu con trỏ chung.
Khi chạy, hầu hết các tham chiếu đối tượng trong CPython được biểu diễn dưới dạng:```c PyObject *
## 8.1 Tiêu đề đối tượng chung
Một sự đơn giản hóa`PyObject`trông như thế này:```c
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;
```Định nghĩa thực sự sử dụng macro và các trường phụ thuộc vào bản dựng, đặc biệt là trong các bản dựng gỡ lỗi, bản dựng theo dõi và các phiên bản CPython hiện đại. Nhưng ý tưởng thiết yếu là ổn định:```text
PyObject
reference count
type pointer
```Số lượng tham chiếu theo dõi quyền sở hữu.
Con trỏ kiểu cho CPython biết đối tượng hoạt động như thế nào.
Mọi đối tượng CPython bình thường đều bắt đầu bằng tiêu đề chung này. Do đó, thời gian chạy có thể nhận được một`PyObject *`và kiểm tra loại của nó mà không biết cấu trúc cụ thể đầy đủ tại thời điểm biên dịch.
## 8.2 Tại sao mọi đối tượng đều bắt đầu theo cùng một cách
Hãy xem xét mã Python này:```python
x = 42
y = "hello"
z = [1, 2, 3]
```Ở cấp độ C, các đối tượng này có bố cục khác nhau.```text
PyLongObject
object header
integer digit data
PyUnicodeObject
object header
string metadata
character storage
PyListObject
object header
length
allocated capacity
pointer to item array
```Nhưng mỗi cái đều bắt đầu với cùng một tiêu đề:```text
+--------------------+
| ob_refcnt |
+--------------------+
| ob_type |
+--------------------+
| type-specific data |
+--------------------+
```Điều này cho phép mã thời gian chạy chung hoạt động với tất cả các đối tượng.
Ví dụ,`Py_INCREF(obj)`chỉ cần trường đếm tham chiếu. Không cần biết liệu`obj`là một danh sách, chuỗi, lệnh hoặc hàm.
Tương tự như vậy,`Py_TYPE(obj)`chỉ cần trường con trỏ kiểu.
## 8.3`ob_refcnt`
`ob_refcnt`lưu trữ số lượng tham chiếu của đối tượng.
Tính tham chiếu là cơ chế tồn tại của đối tượng chính của CPython. Khi mã tạo, lưu trữ, trả về hoặc giải phóng các tham chiếu đối tượng, CPython sẽ cập nhật số lượng này.
Đơn giản hóa:```c
#define Py_INCREF(op) ((op)->ob_refcnt++)
#define Py_DECREF(op) \
do { \
if (--(op)->ob_refcnt == 0) { \
deallocate_object(op); \
} \
} while (0)
```Việc thực hiện thực tế phức tạp hơn. Nó xử lý các đối tượng bất tử, móc gỡ lỗi, truy tìm, xây dựng luồng tự do và chi tiết phân bổ.
Quy tắc khái niệm là:```text
new strong reference acquired
increment reference count
strong reference released
decrement reference count
reference count reaches zero
destroy object
```Ví dụ:```python
x = []
y = x
del x
del y
```Đối tượng danh sách vẫn tồn tại trong khi vẫn còn ít nhất một tham chiếu mạnh.
## 8.4`ob_type`
`ob_type`trỏ tới đối tượng kiểu của đối tượng.
Đối với mã Python này:```python
x = []
```đối tượng danh sách`ob_type`chỉ vào`list`đối tượng gõ.
Về mặt khái niệm:```text
x ---> PyListObject
ob_refcnt
ob_type ----> PyList_Type
ob_size
ob_item
allocated
```Đối tượng loại mô tả hành vi:```text
object name
object size
base classes
method table
attribute lookup behavior
call behavior
deallocation behavior
number operations
sequence operations
mapping operations
```Đây là cách CPython điều động các hoạt động.
Khi mã đánh giá:```python
len(x)
```CPython kiểm tra khe độ dài của loại.
Khi mã đánh giá:```python
x[0]
```CPython kiểm tra trình tự hoặc hành vi ánh xạ.
Khi mã đánh giá:```python
x + y
```CPython kiểm tra các khe nối số hoặc chuỗi tùy thuộc vào loại.
## 8,5`PyObject_HEAD`Các loại tiện ích mở rộng thường không viết các trường theo cách thủ công. Họ sử dụng macro.
Một đối tượng mở rộng có kích thước cố định thường bắt đầu như thế này:```c
typedef struct {
PyObject_HEAD
long value;
} CounterObject;
PyObject_HEADmở rộng đến các trường cần thiết cho tiêu đề đối tượng.
Về mặt khái niệm:```c typedef struct { Py_ssize_t ob_refcnt; PyTypeObject *ob_type; long value; } CounterObject;
## 8.6 Đối tượng có kích thước cố định
Một đối tượng có kích thước cố định có cùng kích thước cấu trúc C cho mọi phiên bản của loại đó.
Hình dạng ví dụ:```c
typedef struct {
PyObject_HEAD
double value;
} FloatLikeObject;
```Mỗi phiên bản đều có chỗ cho chính xác một`double`.
Nhiều đối tượng có kích thước cố định ở cấp độ cấu trúc đối tượng:```text
float
module
function
method
cell
weakref
many iterator objects
many descriptor objects
```Đối tượng vẫn có thể tham chiếu đến dữ liệu bên ngoài hoặc được phân bổ riêng. Kích thước cố định có nghĩa là bản thân cấu trúc đối tượng có kích thước cố định, không phải đối tượng logic hoàn chỉnh không có bộ lưu trữ phụ.
Một đối tượng hàm có kích thước cố định dưới dạng một cấu trúc, nhưng nó trỏ đến các đối tượng khác như đối tượng mã của nó, dict toàn cục, bộ dữ liệu mặc định, bộ dữ liệu đóng và chú thích.
## 8,7`PyVarObject`Sử dụng các đối tượng có kích thước thay đổi`PyVarObject`.
Một bố cục đơn giản hóa:```c
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size;
} PyVarObject;
```Nó mở rộng`PyObject`với một trường bổ sung:```text
ob_size
ob_sizethường lưu trữ kích thước logic của đối tượng.
Ví dụ:text tuple length bytes length integer digit count some internal variable-sized arrays Hình dạng chung:text PyVarObject ob_refcnt ob_type ob_size variable object payload ## 8,8PyObject_VAR_HEADCác loại tiện ích mở rộng có kích thước thay đổi sử dụng:c typedef struct { PyObject_VAR_HEAD PyObject *items[1]; } SmallArrayObject; Về mặt khái niệm:```c
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
Py_ssize_t ob_size;
PyObject *items[1];
} SmallArrayObject;
Mẫu này được sử dụng khi kích thước của đối tượng được cố định sau khi phân bổ.
Tuples là ví dụ kinh điển. Độ dài của bộ dữ liệu không thay đổi sau khi tạo, do đó CPython có thể phân bổ một khối chứa tham chiếu mục và tiêu đề đối tượng.
## 8.9 Cái gì`ob_size`Có nghĩa`ob_size`không có nghĩa là “số byte bị đối tượng này chiếm giữ”.
Nó có nghĩa là một giá trị kích thước cụ thể theo loại.
Đối với một tuple, đó là số phần tử.
Đối với byte, đó là số byte.
Đối với số nguyên dài, nó liên quan đến số chữ số cơ sở bên trong và có thể mã hóa dấu hiệu.
Đối với loại tùy chỉnh, ý nghĩa phụ thuộc vào việc triển khai loại đó.
Điều này rất quan trọng.`ob_size`được diễn giải bằng mã dành riêng cho loại đối tượng.```text
same field
different meaning per type
```Đối tượng kiểu biết cách diễn giải các thể hiện của chính nó.
## 8.10 Bố cục theo bộ
Một bộ dữ liệu là một ví dụ điển hình về một đối tượng có kích thước thay đổi.
Về mặt khái niệm:```c
typedef struct {
PyObject_VAR_HEAD
PyObject *ob_item[1];
} PyTupleObject;
```Một bộ có độ dài 3 được phân bổ dưới dạng một đối tượng có khoảng trống cho ba tham chiếu mục:```text
PyTupleObject
ob_refcnt
ob_type ---> tuple type
ob_size = 3
ob_item[0] ---> object A
ob_item[1] ---> object B
ob_item[2] ---> object C
```Bộ dữ liệu sở hữu các tham chiếu đến các mục của nó. Khi bộ dữ liệu bị hủy, nó sẽ giảm số lượng tham chiếu của từng đối tượng được chứa.
Tuple không sở hữu riêng các đối tượng. Nó sở hữu tài liệu tham khảo.```python
a = []
t = (a,)
```Bộ dữ liệu lưu trữ một tham chiếu đến danh sách. Tên`a`cũng lưu trữ một tham chiếu đến cùng một danh sách.
## 8.11 Bố cục danh sách
Một danh sách cũng có độ dài thay đổi ở cấp độ Python, nhưng việc triển khai nó khác với bộ dữ liệu.
Đối tượng danh sách có cấu trúc có kích thước cố định trỏ đến một mảng tham chiếu mục được phân bổ riêng.
Về mặt khái niệm:```c
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObject;
```Hình dạng:```text
PyListObject
ob_refcnt
ob_type ---> list type
ob_size = current length
ob_item ----> separately allocated array
allocated = current capacity
```Đối với một danh sách:```python
xs = [10, 20, 30]
```hình dạng bộ nhớ xấp xỉ:```text
list object
ob_size = 3
allocated >= 3
ob_item ----+
|
v
[ ptr to 10 ][ ptr to 20 ][ ptr to 30 ][ spare capacity... ]
```Điều này cho phép nối thêm hiệu quả. Danh sách có thể phát triển bằng cách phân bổ lại mảng mục riêng biệt mà không cần di chuyển đối tượng danh sách.
Nhận dạng đối tượng vẫn ổn định:```python
xs = []
before = id(xs)
xs.append(1)
xs.append(2)
xs.append(3)
after = id(xs)
print(before == after) # True
```Mảng nội bộ của danh sách có thể di chuyển. Bản thân đối tượng danh sách vẫn giữ nguyên đối tượng đó.
## 8.12 Tại sao đồ vật không chuyển động
CPython thường không di chuyển các vật thể sống.
MỘT`PyObject *`là một con trỏ trực tiếp. Nhiều phần của CPython và nhiều phần mở rộng của C có thể chứa con trỏ đó.
Nếu CPython di chuyển một đối tượng trong bộ nhớ, nó sẽ phải tìm và cập nhật mọi con trỏ tới đối tượng đó. Điều đó sẽ tốn kém và không tương thích với nhiều mã mở rộng C.
Vì vậy CPython sử dụng mô hình đối tượng không chuyển động.
Hậu quả:```text
object identity can be represented by address in CPython
C extensions can hold PyObject * pointers
objects are not compacted by a moving garbage collector
memory fragmentation must be managed differently
```Đây là một lý do khiến thiết kế bộ cấp phát của CPython trở nên quan trọng.
## 8.13 Truyền giữa các loại đối tượng
Vì mọi đối tượng đều bắt đầu bằng một tiêu đề chung nên CPython có thể truyền các con trỏ đối tượng cụ thể tới`PyObject *`.
Ví dụ:```c
PyObject *obj = (PyObject *)some_list;
```Nhưng việc truyền ngược chỉ an toàn sau khi kiểm tra kiểu.```c
if (PyList_Check(obj)) {
PyListObject *list = (PyListObject *)obj;
}
```Truyền không an toàn có thể làm hỏng bộ nhớ hoặc làm hỏng trình thông dịch.
Mã mở rộng đúng theo mẫu này:```c
static PyObject *
get_size(PyObject *self, PyObject *arg)
{
if (!PyList_Check(arg)) {
PyErr_SetString(PyExc_TypeError, "expected list");
return NULL;
}
Py_ssize_t n = PyList_GET_SIZE(arg);
return PyLong_FromSsize_t(n);
}
```các`PyList_GET_SIZE`macro giả định đối số của nó là một danh sách. Biến thể API đã kiểm tra sẽ an toàn hơn khi loại không chắc chắn.
## 8.14 API đã được kiểm tra và Macro nhanh
CPython hiển thị cả chức năng đã kiểm tra và macro nhanh.
Mẫu đã kiểm tra:```c
Py_ssize_t n = PyList_Size(obj);
```Dạng macro nhanh:```c
Py_ssize_t n = PyList_GET_SIZE(obj);
```Hàm đã kiểm tra xác thực đối tượng và báo lỗi nếu đầu vào không hợp lệ.
Macro giả định đối tượng đã hợp lệ và có thể truy cập trực tiếp vào các trường.
Đánh đổi:
| Mẫu | An toàn | Tốc độ | Trường hợp sử dụng |
| ---------------- | -----: | -----: | -------------------------------- |
| Đã kiểm tra chức năng | Cao hơn | Hạ | Ranh giới công cộng, đầu vào không chắc chắn |
| Macro nhanh | Hạ | Cao hơn | Mã nội bộ sau khi xác thực |
Mẫu này xuất hiện xuyên suốt API C.
## 8.15 Loại Trường Kích thước Đối tượng
Đối tượng loại mô tả kích thước cá thể.
Các trường quan trọng bao gồm:```text
tp_basicsize
tp_itemsize
tp_basicsizelà phần cố định của mỗi trường hợp.tp_itemsizelà kích thước của từng mục có kích thước thay đổi đối với các đối tượng có kích thước thay đổi.
Đối với loại có kích thước cố định:text tp_basicsize = sizeof(MyObject) tp_itemsize = 0 Đối với loại có kích thước thay đổi:text tp_basicsize = base header and fixed fields tp_itemsize = size of each trailing item Phân bổ sau đó có thể tính toán:```text
total size = tp_basicsize + n * tp_itemsize
## 8.16 Đối tượng mở rộng kích thước cố định tối thiểu
Bố cục đối tượng có kích thước cố định tối thiểu:```c
typedef struct {
PyObject_HEAD
long value;
} CounterObject;
```Một bản phác thảo đối tượng loại tối thiểu:```c
static PyTypeObject CounterType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "example.Counter",
.tp_basicsize = sizeof(CounterObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
};
```Điểm quan trọng là cấu trúc.```text
CounterObject starts with PyObject header.
CounterType says how large CounterObject is.
CPython allocates memory according to CounterType.
CPython treats the result as PyObject * at generic boundaries.
```## 8.17 Đối tượng mở rộng có kích thước thay đổi tối thiểu
Bố cục đối tượng có kích thước thay đổi có thể trông giống như:```c
typedef struct {
PyObject_VAR_HEAD
PyObject *items[1];
} FixedArrayObject;
```Đối tượng loại sẽ sử dụng kích thước mục khác 0:```c
static PyTypeObject FixedArrayType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "example.FixedArray",
.tp_basicsize = offsetof(FixedArrayObject, items),
.tp_itemsize = sizeof(PyObject *),
.tp_flags = Py_TPFLAGS_DEFAULT,
};
```Phân bổ sẽ yêu cầu độ dài logic cụ thể.
Về mặt khái niệm:```text
allocate FixedArray with n items
total bytes = tp_basicsize + n * tp_itemsize
ob_size = n
```Bố cục này hữu ích khi biết số lượng tài liệu tham khảo được chứa tại thời điểm tạo và không thay đổi sau đó.
## 8.18 Macro tiêu đề đối tượng
Các macro phổ biến bao gồm:```c
Py_REFCNT(obj)
Py_TYPE(obj)
Py_SIZE(obj)
```Ý nghĩa khái niệm của chúng:```text
Py_REFCNT(obj)
get reference count
Py_TYPE(obj)
get type pointer
Py_SIZE(obj)
get variable-size field
```Ví dụ:```c
PyTypeObject *type = Py_TYPE(obj);
```Và:```c
Py_ssize_t n = Py_SIZE(tuple_obj);
```Mã mở rộng nên ưu tiên các macro và hàm chính thức hơn là truy cập trường trực tiếp. Điều này làm cho mã tương thích hơn với những thay đổi của CPython.
## 8.19 Quyền sở hữu và tiêu đề tham chiếu
Tiêu đề đối tượng lưu trữ số lượng. Bản thân nó không giải thích quyền sở hữu.
Quyền sở hữu là một quy ước được thực thi bởi các quy tắc API.
Một hàm trả về một tham chiếu mới sẽ chuyển quyền sở hữu cho người gọi:```c
PyObject *x = PyLong_FromLong(42);
/* caller owns x */
Py_DECREF(x);
```Hàm trả về tham chiếu đã mượn không chuyển quyền sở hữu:```c
PyObject *item = PyList_GetItem(list, 0);
/* borrowed reference, do not DECREF unless INCREF first */
```Tiêu đề đối tượng giống nhau có liên quan đến cả hai trường hợp. Sự khác biệt là hợp đồng của lệnh gọi API.
Đây là lý do tại sao việc lập trình mở rộng CPython lại khó khăn. Bố cục bộ nhớ đơn giản nhưng các quy tắc sở hữu đòi hỏi phải có tính kỷ luật.
## 8.20 Khởi tạo đối tượng
Phân bổ và khởi tạo là riêng biệt.
Đối với một đối tượng kiểu, việc phân bổ thường được xử lý bởi:```text
tp_alloc
```Việc xây dựng đối tượng có thể bao gồm:```text
tp_new
tp_init
```Ở cấp độ Python:```python
obj = MyClass(...)
```đại khái có nghĩa là:```text
call type object
call tp_new to allocate or return object
call tp_init to initialize object
return object
```Đối với các đối tượng bất biến,`tp_new`thường thực hiện hầu hết công việc vì giá trị phải được thiết lập trước khi đối tượng được hiển thị.
Đối với các đối tượng có thể thay đổi,`tp_init`có thể điền vào trạng thái sau khi phân bổ.
## 8.21 Phân bổ
Khi số lượng tham chiếu của một đối tượng đạt đến 0, CPython sẽ gọi bộ giải phóng theo loại cụ thể.
Đối tượng loại lưu trữ điều này trong:```text
tp_dealloc
```Người giải quyết thường thực hiện các bước sau:```text
release references owned by the object
free auxiliary buffers
untrack from cyclic GC if needed
free object memory
```Đối với một vùng chứa, bộ giải phóng phải giảm các tham chiếu đến các đối tượng được chứa.
Hình dạng ví dụ:```c
static void
Counter_dealloc(CounterObject *self)
{
Py_TYPE(self)->tp_free((PyObject *)self);
}
```Đối với một thùng chứa:```c
static void
Array_dealloc(ArrayObject *self)
{
for (Py_ssize_t i = 0; i < Py_SIZE(self); i++) {
Py_XDECREF(self->items[i]);
}
Py_TYPE(self)->tp_free((PyObject *)self);
}
```Điều này được đơn giản hóa. Mã thực phải xử lý việc theo dõi trình thu gom rác và các bất biến an toàn lỗi.
## 8.22 Tiêu đề thu gom rác
Các đối tượng tham gia thu thập rác theo chu kỳ có thể có tiêu đề GC bổ sung trước phần hiển thị`PyObject`tiêu đề.
Về mặt khái niệm:```text
GC header
PyObject header
type-specific payload
```các`PyObject *`trỏ đến tiêu đề đối tượng, không phải tiêu đề GC.```text
memory block start
GC metadata
ob_refcnt <--- PyObject * points here
ob_type
payload
```Tiêu đề GC liên kết đối tượng vào các cấu trúc bộ sưu tập.
Chỉ những đối tượng giống như thùng chứa có thể tham gia vào chu trình thường mới cần tính năng theo dõi này.
Loại tiện ích mở rộng sở hữu các tham chiếu đến các đối tượng Python khác và có thể tham gia vào các chu trình phải triển khai chính xác giao thức GC.
## 8.23 Bản dựng gỡ lỗi
Bản dựng gỡ lỗi có thể thêm các trường bổ sung hoặc kiểm tra xung quanh các đối tượng.
Điều này có thể bao gồm:```text
reference count debugging
allocator padding
forbidden bytes around memory blocks
extra assertions
API misuse detection
```Vì lý do này, mã mở rộng nên tránh giả định bố cục bộ nhớ thô chính xác ngoài các macro được ghi lại.
Phong cách xấu:```c
obj->ob_refcnt++;
```Phong cách tốt hơn:```c
Py_INCREF(obj);
```Phong cách xấu:```c
obj->ob_type
```Phong cách tốt hơn:```c
Py_TYPE(obj)
```Các macro là lớp tương thích giữa mã mở rộng và nội bộ CPython.
## 8.24 Vật thể bất tử
CPython hiện đại có khái niệm về vật thể bất tử dành cho những vật thể có tuổi thọ cao được chọn. Một đối tượng bất tử sử dụng một giá trị đếm tham chiếu đặc biệt và tránh việc phá hủy số tham chiếu thông thường.
Các ứng cử viên điển hình bao gồm các đối tượng cơ bản giống như singleton hoặc thuộc sở hữu thời gian chạy.
Điểm thiết thực cho độc giả nội bộ:```text
not every reference count behaves like an ordinary small integer
not every INCREF or DECREF has the same runtime effect
never write code that depends on exact refcount arithmetic unless working inside CPython internals
```Ở cấp độ Python, việc kiểm tra số tham chiếu đã được triển khai cụ thể. Ở cấp độ C, tác giả tiện ích mở rộng nên sử dụng API quản lý tham chiếu chính thức.
## 8.25 Tại sao`PyObject`Vấn đề`PyObject`là loại tiền tệ chung của thời gian chạy CPython.
Vòng lặp thông dịch đẩy và bật`PyObject *`các giá trị.
Cuộc gọi chức năng vượt qua`PyObject *`lý lẽ.
Cửa hàng container`PyObject *`tài liệu tham khảo.
API tiện ích mở rộng C nhận và trả lại`PyObject *`.
Loại khe hoạt động trên`PyObject *`.
Lỗi được biểu thị bằng các đối tượng ngoại lệ Python.
Các mô-đun là các đối tượng Python.
Các lớp là các đối tượng Python.
Hàm là các đối tượng Python.
Đối tượng mã là đối tượng Python.
Tính đồng nhất này là điều làm cho Python trở nên năng động. Thời gian chạy có thể thao tác các giá trị tùy ý thông qua một biểu diễn trong khi gửi hành vi thông qua các đối tượng kiểu.
## 8.26 Mô hình tinh thần hữu ích
Khi đọc mã CPython C, trước tiên hãy giả sử hình dạng này:```text
PyObject *
points to object header
ob_refcnt
ob_type
followed by type-specific memory
```Sau đó hỏi:```text
What concrete type is this object expected to be?
Is it fixed-size or variable-size?
Who owns this reference?
Can this object participate in reference cycles?
What type slot handles this operation?
Does this API return a new reference or borrowed reference?
```Những câu hỏi này ngăn ngừa sự nhầm lẫn sớm nhất khi đọc nội bộ CPython.
## 8.27 Tóm tắt`PyObject`là tiêu đề cơ sở cho các đối tượng CPython. Nó cung cấp cho mỗi đối tượng một số tham chiếu và một con trỏ kiểu.`PyVarObject`mở rộng tiêu đề đó bằng trường kích thước được sử dụng bởi các đối tượng có kích thước thay đổi.
Sử dụng các đối tượng có kích thước cố định`PyObject_HEAD`.
Sử dụng các đối tượng có kích thước thay đổi`PyObject_VAR_HEAD`.
Tiêu đề đối tượng làm cho hệ thống đối tượng động của CPython trở nên khả thi. Nó cho phép mã thời gian chạy chung xử lý nhiều bố cục đối tượng cụ thể thông qua`PyObject *`, trong khi các đối tượng kiểu cung cấp hành vi cần thiết cho các cuộc gọi, số học, lập chỉ mục, thuộc tính, phân bổ và gửi giao thức.