9. 引用计数
9. 引用计数
引用计数是 CPython 的主要内存管理机制。每个普通对象都带有当前指向它的强引用计数。当该计数降至零时,CPython 可以立即销毁该对象。
这种设计是 CPython 与许多其他语言运行时之间最明显的区别之一。 CPython 确实有一个循环垃圾收集器,但该收集器补充了引用计数。大多数对象是通过引用计数转换来回收的,而不是通过定期跟踪来回收。
9.1 核心思想
当某个对象拥有对它的引用时,Python 对象就会保持活动状态。
从概念上讲:```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
例子:python
x = []
y = x
del x
del y
```列表对象被创建并绑定到x。装订y = x创建对同一列表的另一个引用。正在删除x删除一个引用。正在删除y删除剩余的引用,因此可以销毁列表。
在 C 级别,这是由对象头控制的:```c typedef struct { Py_ssize_t ob_refcnt; PyTypeObject *ob_type; } PyObject;
`ob_refcnt`是引用计数字段。
## 9.2 强引用
强引用使对象保持活动状态。
大多数普通的 Python 绑定都是强引用:```python
x = object()
items = [x]
d = {"key": x}
```在这里,该对象具有来自以下位置的引用:```text
local name x
list element items[0]
dictionary value d["key"]
```只要至少存在一个强引用,该对象就无法被销毁。
容器对其元素有很强的引用。列表不存储原始值。它存储指向 Python 对象的指针并拥有对它们的引用。```python
a = []
b = [a]
```名单`b`拥有对列表的引用`a`。
## 9.3 借用参考文献和自有参考文献
在 C API 级别,引用计数由所有权规则控制。
常见的有两类:
|参考实物|意义|
| ------------------ | ---------------------------------------------------------------------------------- |
|新参考|调用者拥有该引用并且必须释放它 |
|借用参考|调用者可以暂时使用该对象,但不拥有新的引用 |
新参考示例:```c
PyObject *x = PyLong_FromLong(42);
/* x is a new reference */
Py_DECREF(x);
PyLong_FromLong创建或返回一个对象,并为调用者提供一个引用的所有权。
借用参考文献的示例:```c PyObject *item = PyList_GetItem(list, 0);
/* item is borrowed */
```呼叫者不得呼叫Py_DECREF(item)除非它首先将借用的引用转换为拥有的引用Py_INCREF。
正确模式:```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`记录表明,现在又有一位拥有者拥有强有力的参考。
从概念上讲:```c
Py_INCREF(obj);
```方法:```text
I am going to keep this object alive.
```典型案例:```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
```例子:```c
typedef struct {
PyObject_HEAD
PyObject *value;
} BoxObject;
```如果一个`BoxObject`存储另一个Python对象时,它必须增加引用计数:```c
static int
Box_set_value(BoxObject *self, PyObject *value)
{
Py_INCREF(value);
Py_XDECREF(self->value);
self->value = value;
return 0;
}
```该盒子成为了所有者`value`。
## 9.5`Py_DECREF`
`Py_DECREF`释放引用的所有权。
从概念上讲:```c
Py_DECREF(obj);
```方法:```text
I no longer need to keep this object alive.
```如果引用计数达到零,CPython 就会销毁该对象。
简化:```c
#define Py_DECREF(op) \
do { \
if (--(op)->ob_refcnt == 0) { \
dealloc(op); \
} \
} while (0)
```真正的实现更为复杂,但这抓住了规则。
每个新的参考文献最终都必须发布。```c
PyObject *x = PyLong_FromLong(42);
if (x == NULL) {
return NULL;
}
/* use x */
Py_DECREF(x);
```忘记了`Py_DECREF`泄漏对象。
呼唤`Py_DECREF`太多次可能会破坏仍在使用的对象。
## 9.6`Py_XINCREF`和`Py_XDECREF`一些指针可能是`NULL`。`Py_INCREF`和`Py_DECREF`需要一个有效的对象指针。他们的`X`变体接受`NULL`。```c
Py_XINCREF(obj);
Py_XDECREF(obj);
```它们通常用于可选字段:```c
typedef struct {
PyObject_HEAD
PyObject *name; /* may be NULL */
} UserObject;
```解除分配器:```c
static void
User_dealloc(UserObject *self)
{
Py_XDECREF(self->name);
Py_TYPE(self)->tp_free((PyObject *)self);
}
```如果`self->name`是`NULL`, `Py_XDECREF`什么也不做。
## 9.7 Python 级别的赋值
Python 赋值会更改绑定。```python
x = []
x = {}
```第一个赋值绑定`x`到一个列表。第二个任务重新绑定`x`到一个字典。
在 CPython 层面,重新绑定大致意味着:```text
increment reference to new object
store new pointer in local variable slot
decrement reference to previous object
```顺序很重要。 CPython 必须避免过早销毁对象,尤其是当赋值涉及重叠引用或错误路径时。
例子:```python
x = []
y = x
x = None
```后`x = None`,该列表仍然有效,因为`y`仍然引用它。
## 9.8 Containers Own References
When an object is inserted into a container, the container owns a reference.```python
x = object()
items = []
items.append(x)
```该对象被两者引用`x`和`items[0]`。
在C 级别,容器在存储对象时必须增加引用计数。
简化的列表附加逻辑:```text
append item to list
ensure capacity
increment item reference count
store item pointer
increase list size
```当列表被销毁时,它会释放对其元素的引用:```text
destroy list
for each item:
decrement item reference count
free item array
free list object
```这种所有权模型是递归的。如果不存在其他引用,则销毁容器可能会触发所包含对象的销毁。
## 9.9 安全地替换引用
一种常见的 C 模式是将一个拥有的字段替换为另一个拥有的字段。
错误:```c
Py_DECREF(self->value);
self->value = new_value;
Py_INCREF(new_value);
```如果出现以下情况,这可能会失败`new_value`是同一个对象`self->value`。这`Py_DECREF`可能会在增量发生之前销毁对象。
正确的:```c
Py_INCREF(new_value);
Py_DECREF(self->value);
self->value = new_value;
```对于可为空的字段:```c
Py_XINCREF(new_value);
Py_XDECREF(self->value);
self->value = new_value;
```这种模式很简单但很重要:```text
increment new reference first
decrement old reference second
store pointer when ownership is safe
```## 9.10 参考窃取
一些 C API 函数窃取引用。
窃取引用的函数会从调用者那里获取所有权。成功调用后,调用者不得递减该引用。
示例模式:```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窃取对的引用value关于成功。
此约定的存在是为了提高效率,尤其是在从新创建的对象构建容器时。
引用窃取是错误的常见来源。该规则必须从 API 文档或已知的 C API 约定中读取。无法从对象指针类型推断出来。
9.11 错误路径
引用计数错误经常发生在错误路径上。
例子:```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 */
}
正确的:c
PyObject *a = PyLong_FromLong(1);
if (a == NULL) {
return NULL;
}
PyObject *b = PyLong_FromLong(2);
if (b == NULL) {
Py_DECREF(a);
return NULL;
}
一种常见的样式使用一个清理块: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 返回引用
暴露给 Python 的 C 函数通常返回一个新的引用。
例子:```c
static PyObject *
answer(PyObject *self, PyObject *args)
{
return PyLong_FromLong(42);
}
```返回的对象是一个新的引用。口译员接收该文件并对此负责。
如果返回现有对象,请首先递增:```c
static PyObject *
get_cached_value(MyObject *self, PyObject *Py_UNUSED(ignored))
{
Py_INCREF(self->cached_value);
return self->cached_value;
}
```返回借用的引用而不递增是一个严重的错误:```c
static PyObject *
bad_get_cached_value(MyObject *self, PyObject *Py_UNUSED(ignored))
{
return self->cached_value; /* wrong if this is borrowed */
}
```调用者期望返回值的所有权。如果引用计数未增加,则稍后的减少可能会导致所有权下溢并导致释放后使用行为。
## 9.13`None`, `True`, 和`False`返回`None`很常见。
使用:```c
Py_RETURN_NONE;
```从概念上讲,这会增加`None`并返回它。
等效形状:```c
Py_INCREF(Py_None);
return Py_None;
```相似地:```c
Py_RETURN_TRUE;
Py_RETURN_FALSE;
```这些宏可以避免错误并使意图清晰。
在Python级别:```python
def f():
return None
```在 C 级别,该函数仍必须返回对`None`目的。
## 9.14 临时参考
许多操作都会创建临时引用。
例子:```c
PyObject *a = PyLong_FromLong(10);
PyObject *b = PyLong_FromLong(20);
PyObject *sum = PyNumber_Add(a, b);
```这里,`a`, `b`, 和`sum`如果创建成功,则所有引用都是自有的。
正确清理:```c
Py_DECREF(a);
Py_DECREF(b);
return sum;
```不减量`sum`在返回之前,因为调用者获得了所有权。
这个划分很重要:```text
objects used temporarily inside the function
DECREF before return
object returned to caller
return as owned reference
```## 9.15 引用计数和函数调用
当调用 Python 函数时,参数作为对象引用传递。```python
def f(x):
return x
obj = []
y = f(obj)
```对象未被复制。该函数接收对同一列表的引用。
在解释器级别,函数调用机制管理参数对象、局部变量、堆栈值和返回值的引用。
从概念上讲:```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
```确切的机制得到了优化,但所有权不变性仍然存在:必须考虑每个实时引用。
## 9.16 堆栈引用
字节码解释器在每个帧内使用一个值堆栈。
字节码指令推送和弹出对象引用。
例子:```python
a + b
```从概念上讲:```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
```堆栈条目是引用。当值被弹出并且不再需要时,CPython 必须释放相应的引用。
因此,评估循环是一个大型参考管理系统。它必须在正常执行、异常、跳转、返回和帧拆卸过程中保留所有权。
## 9.17 引用计数和异常
错误处理需要仔细的引用清理。
假设创建临时对象后操作失败:```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;
```如果`PyObject_GetAttr`失败则返回`NULL`并设置一个例外。临时的`name`在返回之前仍然必须递减。
C 函数通过返回来报告异常`NULL`当设置了例外时。
引用清理和异常传播是不同的职责:```text
release owned references
preserve or set exception state
return NULL
```## 9.18 参考泄漏
当强引用从未被释放时,就会发生引用泄漏。
例子:```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 */
}
```正确的:```c
static PyObject *
fixed(PyObject *self, PyObject *args)
{
PyObject *x = PyLong_FromLong(42);
if (x == NULL) {
return NULL;
}
Py_DECREF(x);
Py_RETURN_NONE;
}
```参考泄漏可能很小,但在长时间运行的过程中很严重:```text
web servers
workers
language servers
notebooks
daemon processes
embedded Python runtimes
```热路径中的泄漏可能会无限增长。
## 9.19 释放后使用
当代码在引用的对象被销毁后使用指针时,就会发生释放后使用。
示例模式:```c
PyObject *item = PyList_GetItem(list, 0); /* borrowed */
Py_DECREF(list);
/* item may now be invalid */
PyObject_Repr(item);
```如果`list`拥有最后一个强引用`item`,破坏`list`也被摧毁`item`。
正确的:```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;
```借用的引用不得比其所有者的寿命长,除非转换为自有的引用。
## 9.20 周期
仅引用计数无法回收循环。
例子:```python
a = []
b = []
a.append(b)
b.append(a)
del a
del b
```删除后`a`和`b`,两个列表仍然相互引用。
从概念上讲:```text
list A ---> list B
list B ---> list A
```即使程序无法再访问它们,它们的引用计数也不会达到零。
这就是 CPython 有循环垃圾收集器的原因。它找到无法访问的容器对象组并安全地破坏它们。
引用计数处理常见情况。循环GC处理引用计数看不到的情况。
## 9.21 终结器和销毁时序
CPython 通常会在对象的最后一个引用消失时立即销毁对象。
例子:```python
class Resource:
def __del__(self):
print("destroyed")
r = Resource()
del r
```在 CPython 中,`__del__`通常在之后立即运行`del r`,假设不存在其他参考。
但可移植的 Python 代码应该避免依赖于精确的销毁时间。其他实现可能使用不同的垃圾收集策略。
使用上下文管理器进行确定性资源管理:```python
with open("data.txt") as f:
data = f.read()
```这比依赖文件对象销毁更好:```python
f = open("data.txt")
data = f.read()
f = None
```引用计数可以让 CPython 及时清理,但是`with`直接表达寿命。
## 9.22 引用计数是可观察的,但特定于实现
CPython 通过以下方式公开引用计数`sys.getrefcount`。```python
import sys
x = []
print(sys.getrefcount(x))
```报告的数字通常高于预期,因为通过`x`进入`getrefcount`创建临时参考。
例子:```python
import sys
x = []
print(sys.getrefcount(x)) # often 2, not 1
```一种参考来自当地名称`x`。另一个临时引用来自函数调用。
小心使用这个工具。它对于学习和调试 CPython 行为很有用,但它公开了实现细节。
## 9.23 不朽之物
现代 CPython 对选定的值使用不朽对象,这些值旨在在进程生命周期内有效。
从简单意义上讲,不朽对象的行为与普通的引用对象不同。它的引用计数可以使用特殊值,并且正常的递增或递减操作可以避免改变其有效寿命。
这种优化减少了非常常见对象的开销并简化了一些运行时不变量。
重要的示例包括运行时基础的对象,例如单例值和经常重用的内部对象。
扩展作者的规则仍然很简单:```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
```正确的代码适用于普通和不朽的对象。
## 9.24 自由线程 CPython 和引用计数
传统 CPython 使用全局解释器锁保护许多引用计数更新。在自由线程构建中,引用管理需要额外的机制,因为多个线程可能同时执行 Python 代码。
这会影响内部实现细节,但 C API 所有权规则仍然是概念契约:```text
own a new reference
release it exactly once
borrow a reference
do not release it
keep a borrowed reference longer
increment it first
```内部可能会使用偏向引用计数、延迟引用处理、原子操作或其他技术,具体取决于构建模式和版本。
扩展代码应避免依赖原始引用计数布局或更新机制。
## 9.25 引用计数规则
C 扩展代码的实用规则:
|情况|行动|
| -------------------------------------------------- | ---------------------------------------------------- |
|函数返回一个新的引用 |`Py_DECREF`完成后|
|函数返回借用的引用 |不要`Py_DECREF`|
|将对象存储在字段中 |`Py_INCREF`储存前 |
|替换字段中的对象 |`Py_INCREF`新的,然后`Py_DECREF`旧|
|返回现有对象 |`Py_INCREF`返回之前 |
|获取参考后出错 |返回之前释放拥有的引用 |
|指针可能是`NULL`|使用`Py_XDECREF`|
| API窃取参考|转账成功后请勿释放 |
大多数参考错误都来自违反这些规则之一。
## 9.26 最小正确字段所有者
这是一个拥有一个 Python 对象字段的小对象。```c
typedef struct {
PyObject_HEAD
PyObject *value;
} BoxObject;
```初始化:```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;
}
```解除分配:```c
static void
Box_dealloc(BoxObject *self)
{
Py_XDECREF(self->value);
Py_TYPE(self)->tp_free((PyObject *)self);
}
```吸气剂:```c
static PyObject *
Box_get_value(BoxObject *self, void *closure)
{
Py_INCREF(self->value);
return self->value;
}
```二传手:```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;
}
```这显示了基本的所有权模式:```text
own stored field
release stored field during destruction
return stored field as a new reference
replace field safely
```## 9.27 心智模型
在阅读 CPython C 代码时,请针对每个问题提出以下问题`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?
```最后一个问题很微妙。减少引用可能会破坏对象。销毁一个对象可以调用终结器。终结器可以运行 Python 代码。 Python 代码可以改变全局状态。所以,`Py_DECREF`可能会产生很大的副作用。
## 9.28 总结
引用计数是 CPython 的主要生命周期机制。每个普通对象都会记录有多少个强引用指向它。`Py_INCREF`获得所有权。`Py_DECREF`释放所有权。当计数达到零时,CPython 通过其特定于类型的释放器销毁该对象。
困难的部分不是计数器本身。困难的部分是所有权纪律。 C 扩展代码必须区分新引用、借用引用、窃取引用、可为空引用、临时引用、存储引用和返回引用。
引用计数解释了 CPython 的大部分行为:迅速销毁、许多情况下的确定性清理、扩展模块规则、容器所有权以及对单独循环垃圾收集器的需求。