9. Đếm tham chiếu
#9. Đếm tham khảo
Tính tham chiếu là cơ chế quản lý bộ nhớ chính của CPython. Mỗi đối tượng thông thường đều mang một số lượng tham chiếu mạnh hiện đang trỏ đến nó. Khi số lượng đó giảm xuống 0, CPython có thể phá hủy đối tượng ngay lập tức.
Thiết kế này là một trong những khác biệt rõ ràng nhất giữa CPython và nhiều thời gian chạy ngôn ngữ khác. CPython có một trình thu thập rác theo chu kỳ, nhưng trình thu thập này bổ sung tính năng tham chiếu. Hầu hết các đối tượng được lấy lại bằng cách chuyển đổi số tham chiếu chứ không phải bằng cách theo dõi định kỳ.
9.1 Ý tưởng cốt lõi
Một đối tượng Python vẫn tồn tại trong khi có thứ gì đó sở hữu một tham chiếu đến nó.
Về mặt khái niệm:```text object created reference count = 1
another owner stores the object reference count += 1
an owner releases the object reference count -= 1
reference count reaches 0
object is deallocated
Ví dụ:python
x = []
y = x
del x
del y
```Đối tượng danh sách được tạo và liên kết vớix. Ràng buộcy = xtạo một tham chiếu khác tới cùng danh sách. Đang xóaxloại bỏ một tham chiếu. Đang xóayloại bỏ tham chiếu còn lại, do đó danh sách có thể bị hủy.
Ở cấp độ C, điều này được kiểm soát bởi tiêu đề đối tượng:```c typedef struct { Py_ssize_t ob_refcnt; PyTypeObject *ob_type; } PyObject;
`ob_refcnt`là trường đếm tham chiếu.
## 9.2 Tài liệu tham khảo mạnh
Một tham chiếu mạnh giữ cho một đối tượng tồn tại.
Hầu hết các liên kết Python thông thường đều là các tham chiếu mạnh:```python
x = object()
items = [x]
d = {"key": x}
```Ở đây, đối tượng có các tham chiếu từ:```text
local name x
list element items[0]
dictionary value d["key"]
```Miễn là còn lại ít nhất một tham chiếu mạnh thì đối tượng không thể bị phá hủy.
Các thùng chứa chứa các tham chiếu mạnh đến các phần tử của chúng. Danh sách không lưu trữ giá trị thô. Nó lưu trữ các con trỏ tới các đối tượng Python và sở hữu các tham chiếu đến chúng.```python
a = []
b = [a]
```Danh sách`b`sở hữu một tham chiếu đến danh sách`a`.
## 9.3 Tài liệu tham khảo mượn và tài liệu tham khảo sở hữu
Ở cấp độ API C, việc đếm tham chiếu được kiểm soát bởi các quy tắc quyền sở hữu.
Có hai loại phổ biến:
| Loại tham khảo | Ý nghĩa |
| ------------------ | ---------------------------------------------------------------------- |
| Tài liệu tham khảo mới | Người gọi sở hữu tham chiếu và phải giải phóng nó |
| Tài liệu tham khảo mượn | Người gọi có thể sử dụng đối tượng tạm thời nhưng không sở hữu tài liệu tham khảo mới |
Ví dụ về một tài liệu tham khảo mới:```c
PyObject *x = PyLong_FromLong(42);
/* x is a new reference */
Py_DECREF(x);
PyLong_FromLongtạo hoặc trả về một đối tượng và cấp cho người gọi quyền sở hữu một tham chiếu.
Ví dụ về một tài liệu tham khảo mượn:```c PyObject *item = PyList_GetItem(list, 0);
/* item is borrowed */
```Người gọi không được gọiPy_DECREF(item)trừ khi trước tiên nó chuyển đổi tham chiếu mượn thành tham chiếu sở hữu vớiPy_INCREF.
Đúng mẫu:```c PyObject *item = PyList_GetItem(list, 0); if (item == NULL) { return NULL; }
Py_INCREF(item); /* now item is owned */
/* use item */
Py_DECREF(item);
## 9,4`Py_INCREF`
`Py_INCREF`ghi lại rằng một chủ sở hữu nữa hiện đang nắm giữ một tham chiếu mạnh mẽ.
Về mặt khái niệm:```c
Py_INCREF(obj);
```có nghĩa:```text
I am going to keep this object alive.
```Các trường hợp điển hình:```text
store object into a container
store object into a struct field
return an existing object as a new reference
keep a borrowed reference beyond its safe lifetime
```Ví dụ:```c
typedef struct {
PyObject_HEAD
PyObject *value;
} BoxObject;
```Nếu một`BoxObject`lưu trữ một đối tượng Python khác, nó phải tăng số lượng tham chiếu:```c
static int
Box_set_value(BoxObject *self, PyObject *value)
{
Py_INCREF(value);
Py_XDECREF(self->value);
self->value = value;
return 0;
}
```Chiếc hộp trở thành chủ sở hữu của`value`.
## 9,5`Py_DECREF`
`Py_DECREF`giải phóng quyền sở hữu của một tài liệu tham khảo.
Về mặt khái niệm:```c
Py_DECREF(obj);
```có nghĩa:```text
I no longer need to keep this object alive.
```Nếu số tham chiếu bằng 0, CPython sẽ hủy đối tượng.
Đơn giản hóa:```c
#define Py_DECREF(op) \
do { \
if (--(op)->ob_refcnt == 0) { \
dealloc(op); \
} \
} while (0)
```Việc triển khai thực tế phức tạp hơn, nhưng điều này nắm bắt được quy tắc.
Mọi tài liệu tham khảo mới cuối cùng phải được phát hành.```c
PyObject *x = PyLong_FromLong(42);
if (x == NULL) {
return NULL;
}
/* use x */
Py_DECREF(x);
```quên đi`Py_DECREF`làm rò rỉ vật thể.
Đang gọi`Py_DECREF`quá nhiều lần có thể phá hủy một đồ vật khi nó vẫn đang được sử dụng.
## 9,6`Py_XINCREF`Và`Py_XDECREF`Một số gợi ý có thể`NULL`.
`Py_INCREF`Và`Py_DECREF`yêu cầu một con trỏ đối tượng hợp lệ. Của họ`X`biến thể chấp nhận`NULL`.
```c
Py_XINCREF(obj);
Py_XDECREF(obj);
```Chúng thường được sử dụng cho các trường tùy chọn:```c
typedef struct {
PyObject_HEAD
PyObject *name; /* may be NULL */
} UserObject;
```Người giao dịch:```c
static void
User_dealloc(UserObject *self)
{
Py_XDECREF(self->name);
Py_TYPE(self)->tp_free((PyObject *)self);
}
```Nếu như`self->name`là`NULL`, `Py_XDECREF`không làm gì cả
## 9.7 Bài tập ở cấp độ Python
Bài tập Python thay đổi các ràng buộc.```python
x = []
x = {}
```Nhiệm vụ đầu tiên ràng buộc`x`vào một danh sách. Nhiệm vụ thứ hai trở lại`x`đến một mệnh lệnh.
Ở cấp độ CPython, việc đóng lại có nghĩa là:```text
increment reference to new object
store new pointer in local variable slot
decrement reference to previous object
```Thứ tự quan trọng. CPython phải tránh phá hủy một đối tượng quá sớm, đặc biệt khi các phép gán liên quan đến các tham chiếu chồng chéo hoặc đường dẫn lỗi.
Ví dụ:```python
x = []
y = x
x = None
```Sau đó`x = None`, danh sách vẫn còn tồn tại vì`y`vẫn đề cập đến nó.
## 9.8 Tài liệu tham khảo riêng của vùng chứa
Khi một đối tượng được chèn vào vùng chứa, vùng chứa đó sẽ sở hữu một tham chiếu.```python
x = object()
items = []
items.append(x)
```Đối tượng được tham chiếu bởi cả hai`x`Và`items[0]`.
Ở cấp độ C, vùng chứa phải tăng số lượng tham chiếu khi lưu trữ một đối tượng.
Logic nối danh sách đơn giản hóa:```text
append item to list
ensure capacity
increment item reference count
store item pointer
increase list size
```Khi danh sách bị hủy, nó sẽ giải phóng các tham chiếu đến các phần tử của nó:```text
destroy list
for each item:
decrement item reference count
free item array
free list object
```Mô hình sở hữu này có tính đệ quy. Việc phá hủy một vùng chứa có thể kích hoạt việc phá hủy các đối tượng được chứa nếu không có tham chiếu nào khác tồn tại.
## 9.9 Thay thế tài liệu tham khảo một cách an toàn
Mẫu C phổ biến đang thay thế một trường thuộc sở hữu bằng một trường khác.
Không đúng:```c
Py_DECREF(self->value);
self->value = new_value;
Py_INCREF(new_value);
```Điều này có thể thất bại nếu`new_value`là cùng một đối tượng như`self->value`. các`Py_DECREF`có thể phá hủy đối tượng trước khi sự gia tăng xảy ra.
Chính xác:```c
Py_INCREF(new_value);
Py_DECREF(self->value);
self->value = new_value;
```Đối với các trường có giá trị rỗng:```c
Py_XINCREF(new_value);
Py_XDECREF(self->value);
self->value = new_value;
```Mẫu này đơn giản nhưng quan trọng:```text
increment new reference first
decrement old reference second
store pointer when ownership is safe
```## 9.10 Ăn cắp tài liệu tham khảo
Một số hàm API C lấy cắp tài liệu tham khảo.
Hàm đánh cắp tham chiếu sẽ chiếm quyền sở hữu từ người gọi. Sau khi gọi thành công, người gọi không được giảm tham chiếu đó.
Mẫu ví dụ:```c
PyObject *value = PyLong_FromLong(42);
if (value == NULL) {
return NULL;
}
if (PyTuple_SetItem(tuple, 0, value) < 0) {
Py_DECREF(value);
return NULL;
}
/* do not Py_DECREF(value) here */
PyTuple_SetItemđánh cắp một tài liệu tham khảo đếnvaluevề sự thành công.
Quy ước này tồn tại để mang lại hiệu quả, đặc biệt khi xây dựng các thùng chứa từ các đối tượng mới được tạo.
Ăn cắp tài liệu tham khảo là một nguồn lỗi thường xuyên. Quy tắc phải được đọc từ tài liệu API hoặc quy ước API C đã biết. Nó không thể được suy ra từ kiểu con trỏ đối tượng.
9.11 Đường dẫn lỗi
Lỗi đếm tham chiếu thường xảy ra trên các đường dẫn lỗi.
Ví dụ:```c PyObject *a = PyLong_FromLong(1); if (a == NULL) { return NULL; }
PyObject b = PyLong_FromLong(2);
if (b == NULL) {
return NULL; / leak: a was not released */
}
Chính xác:c
PyObject *a = PyLong_FromLong(1);
if (a == NULL) {
return NULL;
}
PyObject *b = PyLong_FromLong(2);
if (b == NULL) {
Py_DECREF(a);
return NULL;
}
Một phong cách phổ biến sử dụng một khối dọn dẹp:c
PyObject *a = NULL;
PyObject *b = NULL;
PyObject *result = NULL;
a = PyLong_FromLong(1); if (a == NULL) { goto error; }
b = PyLong_FromLong(2); if (b == NULL) { goto error; }
result = PyNumber_Add(a, b); if (result == NULL) { goto error; }
Py_DECREF(a); Py_DECREF(b); return result;
error: Py_XDECREF(a); Py_XDECREF(b); return NULL;
## 9.12 Trả lại tài liệu tham khảo
Hàm C tiếp xúc với Python thường trả về một tham chiếu mới.
Ví dụ:```c
static PyObject *
answer(PyObject *self, PyObject *args)
{
return PyLong_FromLong(42);
}
```Đối tượng được trả về là một tham chiếu mới. Người thông dịch nhận nó và chịu trách nhiệm về nó.
Nếu trả về một đối tượng hiện có, hãy tăng trước:```c
static PyObject *
get_cached_value(MyObject *self, PyObject *Py_UNUSED(ignored))
{
Py_INCREF(self->cached_value);
return self->cached_value;
}
```Trả lại một tham chiếu đã mượn mà không tăng dần là một lỗi nghiêm trọng:```c
static PyObject *
bad_get_cached_value(MyObject *self, PyObject *Py_UNUSED(ignored))
{
return self->cached_value; /* wrong if this is borrowed */
}
```Người gọi mong đợi quyền sở hữu giá trị trả về. Nếu số lượng tham chiếu không được tăng lên thì các lần giảm sau này có thể làm giảm quyền sở hữu và gây ra hành vi sử dụng sau khi không sử dụng.
## 9.13`None`, `True`, Và`False`Trở về`None`là phổ biến.
Sử dụng:```c
Py_RETURN_NONE;
```Về mặt khái niệm, điều này tăng lên`None`và trả lại nó.
Hình dạng tương đương:```c
Py_INCREF(Py_None);
return Py_None;
```Tương tự:```c
Py_RETURN_TRUE;
Py_RETURN_FALSE;
```Những macro này tránh sai lầm và làm cho ý định rõ ràng.
Ở cấp độ Python:```python
def f():
return None
```Ở cấp độ C, hàm vẫn phải trả về một tham chiếu sở hữu cho`None`sự vật.
## 9.14 Tài liệu tham khảo tạm thời
Nhiều thao tác tạo ra các tham chiếu tạm thời.
Ví dụ:```c
PyObject *a = PyLong_FromLong(10);
PyObject *b = PyLong_FromLong(20);
PyObject *sum = PyNumber_Add(a, b);
```Đây,`a`, `b`, Và`sum`đều là tài liệu tham khảo được sở hữu nếu việc tạo thành công.
Dọn dẹp đúng cách:```c
Py_DECREF(a);
Py_DECREF(b);
return sum;
```Đừng giảm`sum`trước khi trả lại vì người gọi sẽ nhận được quyền sở hữu.
Sự phân chia này rất quan trọng:```text
objects used temporarily inside the function
DECREF before return
object returned to caller
return as owned reference
```## 9.15 Số lượng tham chiếu và lệnh gọi hàm
Khi một hàm Python được gọi, các đối số sẽ được truyền dưới dạng tham chiếu đối tượng.```python
def f(x):
return x
obj = []
y = f(obj)
```Đối tượng không được sao chép. Hàm nhận được một tham chiếu đến cùng một danh sách.
Ở cấp độ trình thông dịch, máy gọi hàm quản lý các tham chiếu cho các đối tượng đối số, biến cục bộ, giá trị ngăn xếp và giá trị trả về.
Về mặt khái niệm:```text
caller has reference to obj
call passes reference into callee frame
callee local x refers to same object
return value refers to same object
callee frame is cleared
caller receives returned reference
```Cơ chế chính xác được tối ưu hóa nhưng tính bất biến về quyền sở hữu vẫn được giữ nguyên: mọi tham chiếu trực tiếp đều phải được tính đến.
## 9.16 Tài liệu tham khảo ngăn xếp
Trình thông dịch mã byte sử dụng ngăn xếp giá trị bên trong mỗi khung.
Hướng dẫn mã byte đẩy và bật các tham chiếu đối tượng.
Ví dụ:```python
a + b
```Về mặt khái niệm:```text
LOAD_FAST a
push reference to a
LOAD_FAST b
push reference to b
BINARY_OP +
pop two references
compute result
push result reference
```Các mục ngăn xếp là tài liệu tham khảo. Khi các giá trị được bật lên và không còn cần thiết nữa, CPython phải giải phóng các tham chiếu tương ứng.
Do đó, vòng đánh giá là một hệ thống quản lý tham chiếu lớn. Nó phải bảo toàn quyền sở hữu trong quá trình thực thi thông thường, ngoại lệ, nhảy, trả về và phá bỏ khung.
## 9.17 Đếm tham chiếu và ngoại lệ
Xử lý lỗi yêu cầu dọn dẹp tham chiếu cẩn thận.
Giả sử một thao tác không thành công sau khi tạo các đối tượng tạm thời:```c
PyObject *name = PyUnicode_FromString("field");
if (name == NULL) {
return NULL;
}
PyObject *value = PyObject_GetAttr(obj, name);
Py_DECREF(name);
if (value == NULL) {
return NULL;
}
return value;
```Nếu như`PyObject_GetAttr`thất bại, nó quay trở lại`NULL`và đặt một ngoại lệ. tạm thời`name`vẫn phải được giảm bớt trước khi quay trở lại.
Hàm C báo cáo một ngoại lệ bằng cách trả về`NULL`trong khi một ngoại lệ được thiết lập.
Việc dọn dẹp tham chiếu và truyền bá ngoại lệ là những trách nhiệm riêng biệt:```text
release owned references
preserve or set exception state
return NULL
```## 9.18 Rò rỉ tham khảo
Rò rỉ tham chiếu xảy ra khi một tham chiếu mạnh không bao giờ được phát hành.
Ví dụ:```c
static PyObject *
leaky(PyObject *self, PyObject *args)
{
PyObject *x = PyLong_FromLong(42);
if (x == NULL) {
return NULL;
}
Py_RETURN_NONE; /* leak: x was never DECREFed */
}
```Chính xác:```c
static PyObject *
fixed(PyObject *self, PyObject *args)
{
PyObject *x = PyLong_FromLong(42);
if (x == NULL) {
return NULL;
}
Py_DECREF(x);
Py_RETURN_NONE;
}
```Rò rỉ tham chiếu có thể nhỏ nhưng nghiêm trọng trong các quy trình chạy dài:```text
web servers
workers
language servers
notebooks
daemon processes
embedded Python runtimes
```Rò rỉ trên đường dẫn nóng có thể phát triển không giới hạn.
## 9.19 Sử dụng sau miễn phí
Việc sử dụng miễn phí xảy ra khi mã sử dụng một con trỏ sau khi đối tượng được tham chiếu đã bị hủy.
Mẫu ví dụ:```c
PyObject *item = PyList_GetItem(list, 0); /* borrowed */
Py_DECREF(list);
/* item may now be invalid */
PyObject_Repr(item);
```Nếu như`list`sở hữu tài liệu tham khảo mạnh mẽ cuối cùng về`item`, phá hủy`list`cũng bị phá hủy`item`.
Chính xác:```c
PyObject *item = PyList_GetItem(list, 0);
if (item == NULL) {
return NULL;
}
Py_INCREF(item);
Py_DECREF(list);
/* item is still alive */
PyObject *repr = PyObject_Repr(item);
Py_DECREF(item);
return repr;
```Các tham chiếu mượn không được tồn tại lâu hơn chủ sở hữu của chúng trừ khi được chuyển đổi thành các tham chiếu được sở hữu.
## 9,20 Chu kỳ
Việc tính tham chiếu một mình không thể lấy lại chu kỳ.
Ví dụ:```python
a = []
b = []
a.append(b)
b.append(a)
del a
del b
```Sau khi xóa`a`Và`b`, hai danh sách vẫn tham chiếu lẫn nhau.
Về mặt khái niệm:```text
list A ---> list B
list B ---> list A
```Số lượng tham chiếu của họ không đạt tới 0, mặc dù chương trình không thể tiếp cận họ được nữa.
Đây là lý do tại sao CPython có trình thu gom rác theo chu kỳ. Nó tìm thấy các nhóm đối tượng chứa không thể truy cập được và phá vỡ chúng một cách an toàn.
Tính tham chiếu xử lý trường hợp phổ biến. GC tuần hoàn xử lý các trường hợp đếm tham chiếu không thể nhìn thấy.
## 9.21 Công cụ hoàn thiện và thời gian hủy diệt
CPython thường phá hủy các đối tượng ngay lập tức khi tham chiếu cuối cùng của chúng biến mất.
Ví dụ:```python
class Resource:
def __del__(self):
print("destroyed")
r = Resource()
del r
```Trong CPython,`__del__`thường chạy ngay sau đó`del r`, giả sử không có tài liệu tham khảo nào khác tồn tại.
Nhưng mã Python di động nên tránh dựa vào thời gian hủy chính xác. Các triển khai khác có thể sử dụng các chiến lược thu gom rác khác nhau.
Sử dụng trình quản lý bối cảnh để quản lý tài nguyên xác định:```python
with open("data.txt") as f:
data = f.read()
```Điều này tốt hơn là dựa vào việc phá hủy đối tượng tệp:```python
f = open("data.txt")
data = f.read()
f = None
```Việc đếm tham chiếu giúp dọn dẹp nhanh chóng CPython, nhưng`with`thể hiện trực tiếp cuộc đời.
## 9.22 Số lượng tham chiếu có thể quan sát được nhưng cụ thể khi triển khai
CPython hiển thị số lượng tham chiếu thông qua`sys.getrefcount`.
```python
import sys
x = []
print(sys.getrefcount(x))
```Con số được báo cáo thường cao hơn dự kiến vì đã vượt qua`x`vào trong`getrefcount`tạo ra một tham chiếu tạm thời.
Ví dụ:```python
import sys
x = []
print(sys.getrefcount(x)) # often 2, not 1
```Một tài liệu tham khảo là từ tên địa phương`x`. Một tham chiếu tạm thời khác là từ lệnh gọi hàm.
Sử dụng công cụ này một cách cẩn thận. Nó rất hữu ích cho việc tìm hiểu và gỡ lỗi hành vi CPython, nhưng nó tiết lộ chi tiết triển khai.
## 9.23 Vật thể bất tử
CPython hiện đại sử dụng các đối tượng bất tử cho các giá trị được chọn nhằm tồn tại trong suốt vòng đời của quy trình.
Một vật thể bất tử không hoạt động giống như một vật thể được kể lại thông thường theo nghĩa đơn giản. Số tham chiếu của nó có thể sử dụng một giá trị đặc biệt và các hoạt động tăng hoặc giảm thông thường có thể tránh làm thay đổi thời gian tồn tại hiệu quả của nó.
Việc tối ưu hóa này giúp giảm chi phí chung cho các đối tượng rất phổ biến và đơn giản hóa một số bất biến trong thời gian chạy.
Các ví dụ quan trọng bao gồm các đối tượng cơ bản cho thời gian chạy, chẳng hạn như các giá trị giống như singleton và các đối tượng bên trong thường được sử dụng lại.
Quy tắc dành cho tác giả phần mở rộng vẫn đơn giản:```text
always use Py_INCREF, Py_DECREF, Py_XINCREF, and Py_XDECREF
do not inspect or modify ob_refcnt directly
do not assume reference counts are ordinary small integers
```Mã đúng hoạt động với các vật thể thông thường và bất tử.
## 9.24 CPython có luồng miễn phí và tính toán tham chiếu
CPython truyền thống bảo vệ nhiều bản cập nhật số lượng tham chiếu bằng Khóa phiên dịch toàn cầu. Trong các bản dựng có luồng tự do, việc quản lý tham chiếu cần có máy móc bổ sung vì nhiều luồng có thể thực thi mã Python cùng một lúc.
Điều này ảnh hưởng đến chi tiết triển khai nội bộ, nhưng quy tắc quyền sở hữu API C vẫn là hợp đồng khái niệm:```text
own a new reference
release it exactly once
borrow a reference
do not release it
keep a borrowed reference longer
increment it first
```Nội bộ có thể sử dụng tính năng tham chiếu sai lệch, xử lý tham chiếu trì hoãn, hoạt động nguyên tử hoặc các kỹ thuật khác tùy thuộc vào chế độ và phiên bản xây dựng.
Mã mở rộng nên tránh dựa vào bố cục số tham chiếu thô hoặc cơ chế cập nhật.
## 9.25 Kỷ luật đếm tham khảo
Một nguyên tắc thực tế cho mã mở rộng C:
| Tình huống | Hành động |
| ------------------------------------- | ----------------------------------------- |
| Hàm trả về một tham chiếu mới |`Py_DECREF`khi hoàn thành |
| Hàm trả về một tham chiếu đã mượn | Đừng`Py_DECREF`|
| Lưu trữ đối tượng trong một trường |`Py_INCREF`trước khi lưu trữ |
| Thay thế đối tượng trong một trường |`Py_INCREF`thì mới`Py_DECREF`cũ |
| Trả về đối tượng hiện có |`Py_INCREF`trước khi quay lại |
| Lỗi sau khi lấy tài liệu tham khảo | Phát hành các tài liệu tham khảo đã sở hữu trước khi quay lại |
| Con trỏ có thể`NULL`| Sử dụng`Py_XDECREF`|
| API đánh cắp tài liệu tham khảo | Không giải phóng sau khi chuyển thành công |
Hầu hết các lỗi tham chiếu đều đến từ việc vi phạm một trong các quy tắc này.
## 9.26 Chủ sở hữu trường đúng tối thiểu
Đây là một đối tượng nhỏ sở hữu một trường đối tượng Python.```c
typedef struct {
PyObject_HEAD
PyObject *value;
} BoxObject;
```Khởi tạo:```c
static int
Box_init(BoxObject *self, PyObject *args, PyObject *kwds)
{
PyObject *value = NULL;
if (!PyArg_ParseTuple(args, "O", &value)) {
return -1;
}
Py_INCREF(value);
self->value = value;
return 0;
}
```Phân bổ:```c
static void
Box_dealloc(BoxObject *self)
{
Py_XDECREF(self->value);
Py_TYPE(self)->tp_free((PyObject *)self);
}
```Người nhận:```c
static PyObject *
Box_get_value(BoxObject *self, void *closure)
{
Py_INCREF(self->value);
return self->value;
}
```Người định cư:```c
static int
Box_set_value(BoxObject *self, PyObject *value, void *closure)
{
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "cannot delete value");
return -1;
}
Py_INCREF(value);
Py_XDECREF(self->value);
self->value = value;
return 0;
}
```Điều này cho thấy mô hình sở hữu cơ bản:```text
own stored field
release stored field during destruction
return stored field as a new reference
replace field safely
```## 9.27 Mô hình tinh thần
Khi đọc mã CPython C, hãy hỏi những câu hỏi này cho mọi`PyObject *`:
```text
Who owns this reference?
Was this returned as a new reference or borrowed reference?
Can this pointer be NULL?
What happens on the error path?
Does this container steal the reference?
Does this field need to INCREF when assigned?
Does this deallocator DECREF everything it owns?
Can a DECREF run arbitrary Python code through finalizers?
```Câu hỏi cuối cùng thật tế nhị. Giảm tham chiếu có thể phá hủy một đối tượng. Việc phá hủy một đối tượng có thể gọi các bộ hoàn thiện. Trình hoàn thiện có thể chạy mã Python. Mã Python có thể thay đổi trạng thái toàn cầu. Vì thế,`Py_DECREF`có thể có tác dụng phụ lớn.
##9.28 Tóm tắt
Tính tham chiếu là cơ chế tồn tại chính của CPython. Mỗi đối tượng thông thường ghi lại số lượng tham chiếu mạnh trỏ đến nó.`Py_INCREF`giành được quyền sở hữu.`Py_DECREF`giải phóng quyền sở hữu. Khi số đếm đạt đến 0, CPython sẽ phá hủy đối tượng thông qua bộ giải phóng dành riêng cho loại của nó.
Phần khó khăn không phải là bộ đếm. Phần khó khăn là kỷ luật sở hữu. Mã mở rộng C phải phân biệt các tham chiếu mới, tham chiếu mượn, tham chiếu bị đánh cắp, tham chiếu vô giá trị, tham chiếu tạm thời, tham chiếu được lưu trữ và tham chiếu được trả về.
Việc đếm tham chiếu giải thích phần lớn hành vi của CPython: hủy bỏ kịp thời, dọn dẹp xác định trong nhiều trường hợp, quy tắc mô-đun mở rộng, quyền sở hữu vùng chứa và nhu cầu về một trình thu gom rác tuần hoàn riêng biệt.