#8.PyObjectPyVarObject

PyObjectPyVarObject是 CPython 对象背后的基本布局。它们不是 Python 类。它们是 C 级结构约定,允许运行时通过通用指针类型处理许多不同的对象实现。

在运行时,CPython 中的大多数对象引用表示为:```c PyObject *


## 8.1 公共对象头

一个简化的`PyObject`看起来像这样:```c
typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;
```真正的定义使用宏和依赖于构建的字段,特别是在调试构建、跟踪构建和现代 CPython 版本中。但本质思想是稳定的:```text
PyObject
    reference count
    type pointer
```引用计数跟踪所有权。

类型指针告诉 CPython 对象的行为方式。

每个普通的 CPython 对象都以这个公共标头开始。因此,运行时可以接收`PyObject *`并在编译时不知道完整的具体结构的情况下检查其类型。

## 8.2 为什么每个对象都以相同的方式启动

考虑下面的 Python 代码:```python
x = 42
y = "hello"
z = [1, 2, 3]
```C层,这些对象有不同的布局。```text
PyLongObject
    object header
    integer digit data

PyUnicodeObject
    object header
    string metadata
    character storage

PyListObject
    object header
    length
    allocated capacity
    pointer to item array
```但每个都以相同的标头开头:```text
+--------------------+
| ob_refcnt          |
+--------------------+
| ob_type            |
+--------------------+
| type-specific data |
+--------------------+
```这允许通用运行时代码与所有对象一起使用。

例如,`Py_INCREF(obj)`只需要引用计数字段。它不需要知道是否`obj`是列表、字符串、字典或函数。

同样地,`Py_TYPE(obj)`只需要类型指针字段。

## 8.3`ob_refcnt`

`ob_refcnt`存储对象的引用计数。

引用计数是 CPython 的主要对象生命周期机制。当代码创建、存储、返回或释放对象引用时,CPython 会更新此计数。

简化:```c
#define Py_INCREF(op) ((op)->ob_refcnt++)
#define Py_DECREF(op)                         \
    do {                                      \
        if (--(op)->ob_refcnt == 0) {         \
            deallocate_object(op);            \
        }                                     \
    } while (0)
```真正的实现更为复杂。它处理不朽对象、调试挂钩、跟踪、自由线程构建和释放细节。

概念规则是:```text
new strong reference acquired
    increment reference count

strong reference released
    decrement reference count

reference count reaches zero
    destroy object
```例子:```python
x = []
y = x
del x
del y
```列表对象将继续存在,同时至少保留一个强引用。

## 8.4`ob_type`

`ob_type`指向对象的类型对象。

对于这段 Python 代码:```python
x = []
```列表对象的`ob_type`指向`list`类型对象。

从概念上讲:```text
x  --->  PyListObject
            ob_refcnt
            ob_type  ---->  PyList_Type
            ob_size
            ob_item
            allocated
```类型对象描述行为:```text
object name
object size
base classes
method table
attribute lookup behavior
call behavior
deallocation behavior
number operations
sequence operations
mapping operations
```这就是 CPython 调度操作的方式。

当代码评估时:```python
len(x)
```CPython 检查类型的长度槽。

当代码评估时:```python
x[0]
```CPython 检查序列或映射行为。

当代码评估时:```python
x + y
```CPython 根据类型检查数字或序列连接槽。

## 8.5`PyObject_HEAD`扩展类型通常不会手动写入字段。他们使用宏。

固定大小的扩展对象通常是这样开始的:```c
typedef struct {
    PyObject_HEAD
    long value;
} CounterObject;

PyObject_HEAD扩展为对象标头所需的字段。

从概念上讲:```c typedef struct { Py_ssize_t ob_refcnt; PyTypeObject *ob_type; long value; } CounterObject;


## 8.6 固定大小的对象

固定大小对象对于该类型的每个实例都具有相同的 C 结构体大小。

形状示例:```c
typedef struct {
    PyObject_HEAD
    double value;
} FloatLikeObject;
```每个实例都有足够的空间容纳一个实例`double`

许多对象在对象结构级别是固定大小的:```text
float
module
function
method
cell
weakref
many iterator objects
many descriptor objects
```该对象仍然可以引用外部或单独分配的数据。固定大小是指对象结构体本身有固定的大小,并不是说完整的逻辑对象没有辅助存储。

函数对象作为结构体是固定大小的,但它指向其他对象,例如其代码对象、全局字典、默认元组、闭包元组和注释。

## 8.7`PyVarObject`可变大小对象的使用`PyVarObject`。

简化的布局:```c
typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size;
} PyVarObject;
```它延伸`PyObject`还有一个附加字段:```text
ob_size

ob_size通常存储对象的逻辑大小。

示例:text tuple length bytes length integer digit count some internal variable-sized arrays 常见形状:text PyVarObject ob_refcnt ob_type ob_size variable object payload ## 8.8PyObject_VAR_HEAD可变大小扩展类型使用:c typedef struct { PyObject_VAR_HEAD PyObject *items[1]; } SmallArrayObject; 从概念上讲:```c typedef struct { Py_ssize_t ob_refcnt; PyTypeObject *ob_type; Py_ssize_t ob_size; PyObject *items[1]; } SmallArrayObject;


当对象的大小在分配后固定时使用此模式。

元组是典型的例子。元组的长度在创建后不会改变,因此 CPython 可以分配一个包含对象头和项引用的块。

## 8.9 什么`ob_size`方法`ob_size`并不意味着“该对象占用的字节数”。

它表示特定于类型的大小值。

对于元组来说,它是元素的数量。

对于字节来说,它是字节数。

对于长整数,它与内部基数位数有关,并且可以对符号进行编码。

对于自定义类型,含义取决于类型的实现。

这很重要。`ob_size`由对象的特定于类型的代码解释。```text
same field
different meaning per type
```类型对象知道如何解释它自己的实例。

## 8.10 元组布局

元组是可变大小对象的一个很好的例子。

从概念上讲:```c
typedef struct {
    PyObject_VAR_HEAD
    PyObject *ob_item[1];
} PyTupleObject;
```长度为 3 的元组被分配为一个对象,并具有用于三个项目引用的空间:```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
```The tuple owns references to its items.当元组被销毁时,它会减少每个包含对象的引用计数。

The tuple does not own the objects exclusively.它拥有参考文献。```python
a = []
t = (a,)
```元组存储对列表的引用。名称`a`还存储对同一列表的引用。

## 8.11 列表布局

列表在 Python 级别也是可变长度的,但其实现与元组不同。

列表对象具有固定大小的结构,该结构指向单独分配的项目引用数组。

从概念上讲:```c
typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;
```形状:```text
PyListObject
    ob_refcnt
    ob_type  ---> list type
    ob_size = current length
    ob_item  ----> separately allocated array
    allocated = current capacity
```对于列表:```python
xs = [10, 20, 30]
```记忆形状大约是:```text
list object
    ob_size = 3
    allocated >= 3
    ob_item ----+
                |
                v
              [ ptr to 10 ][ ptr to 20 ][ ptr to 30 ][ spare capacity... ]
```这允许有效的附加。列表可以通过重新分配单独的项目数组来增长,而无需移动列表对象本身。

对象身份保持稳定:```python
xs = []
before = id(xs)

xs.append(1)
xs.append(2)
xs.append(3)

after = id(xs)

print(before == after)   # True
```列表的内部数组可能会移动。列表对象本身仍然是同一对象。

## 8.12 为什么物体不动

CPython 通常不移动活动对象。

一个`PyObject *`是一个直接指针。 CPython 的许多部分和许多 C 扩展都可能保存该指针。

如果 CPython 在内存中移动一个对象,它必须查找并更新指向该对象的每个指针。这将是昂贵的并且与许多 C 扩展代码不兼容。

因此CPython使用非移动对象模型。

结果:```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
```这是 CPython 的分配器设计很重要的原因之一。

## 8.13 对象类型之间的转换

因为每个对象都以公共标头开头,所以 CPython 可以将具体的对象指针转换为`PyObject *`

例子:```c
PyObject *obj = (PyObject *)some_list;
```但反向转换只有在类型检查之后才是安全的。```c
if (PyList_Check(obj)) {
    PyListObject *list = (PyListObject *)obj;
}
```不安全的转换可能会损坏内存或使解释器崩溃。

正确的扩展代码遵循以下模式:```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);
}
````PyList_GET_SIZE`宏假设它的参数是一个列表。当类型不确定时,检查的 API 变体更安全。

## 8.14 检查 API 和快速宏

CPython 公开了检查函数和快速宏。

检查表格:```c
Py_ssize_t n = PyList_Size(obj);
```快速宏形式:```c
Py_ssize_t n = PyList_GET_SIZE(obj);
```checked 函数验证对象并在输入无效时报告错误。

该宏假设对象已经有效并且可以直接访问字段。

权衡:

|表格 |安全|  速度|使用案例|
| ---------------- | -----: | -----: | -------------------------------- |
|检查功能 |更高 |  降低|公共边界,不确定输入 |
|快速宏|  降低|更高 |验证后的内部代码 |

这种模式出现在整个 C API 中。

## 8.15 类型对象大小字段

类型对象描述实例大小。

重要字段包括:```text
tp_basicsize
tp_itemsize

tp_basicsize是每个实例的固定部分。tp_itemsize是可变大小对象的每个可变大小项目的大小。

对于固定大小类型:text tp_basicsize = sizeof(MyObject) tp_itemsize = 0 对于可变大小类型:text tp_basicsize = base header and fixed fields tp_itemsize = size of each trailing item 然后分配可以计算:```text total size = tp_basicsize + n * tp_itemsize


## 8.16 最小固定大小扩展对象

最小的固定大小对象布局:```c
typedef struct {
    PyObject_HEAD
    long value;
} CounterObject;
```最小类型对象草图:```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,
};
```重要的一点是结构性的。```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 最小可变大小扩展对象

可变大小的对象布局可能如下所示:```c
typedef struct {
    PyObject_VAR_HEAD
    PyObject *items[1];
} FixedArrayObject;
```类型对象将使用非零项目大小:```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,
};
```分配将请求特定的逻辑长度。

从概念上讲:```text
allocate FixedArray with n items
    total bytes = tp_basicsize + n * tp_itemsize
    ob_size = n
```当包含的引用数量在创建时已知并且之后不会更改时,此布局非常有用。

## 8.18 对象头宏

常见的宏包括:```c
Py_REFCNT(obj)
Py_TYPE(obj)
Py_SIZE(obj)
```它们的概念意义:```text
Py_REFCNT(obj)
    get reference count

Py_TYPE(obj)
    get type pointer

Py_SIZE(obj)
    get variable-size field
```例如:```c
PyTypeObject *type = Py_TYPE(obj);
```和:```c
Py_ssize_t n = Py_SIZE(tuple_obj);
```扩展代码应该优先使用官方宏和函数而不是直接字段访问。这使得代码与 CPython 更改更加兼容。

## 8.19 引用所有权和标头

对象头存储计数。它本身并不能解释所有权。

所有权是 API 规则强制执行的约定。

返回新引用的函数将所有权转移给调用者:```c
PyObject *x = PyLong_FromLong(42);
/* caller owns x */
Py_DECREF(x);
```返回借用引用的函数不会转移所有权:```c
PyObject *item = PyList_GetItem(list, 0);
/* borrowed reference, do not DECREF unless INCREF first */
```两种情况都涉及相同的对象头。区别在于 API 调用的约定。

这就是 CPython 扩展编程困难的原因。内存布局很简单,但所有权规则需要纪律。

## 8.20 对象初始化

分配和初始化是分开的。

对于类型对象,分配通常由以下方式处理:```text
tp_alloc
```对象构造可能涉及:```text
tp_new
tp_init
```Python级别:```python
obj = MyClass(...)
```大致意思是:```text
call type object
    call tp_new to allocate or return object
    call tp_init to initialize object
    return object
```对于不可变对象,`tp_new`通常会完成大部分工作,因为必须在暴露对象之前建立值。

对于可变对象,`tp_init`分配后可以填写state

## 8.21 释放

当对象的引用计数达到零时,CPython 会调用特定于类型的释放器。

类型对象将其存储在:```text
tp_dealloc
```释放器通常执行以下步骤:```text
release references owned by the object
free auxiliary buffers
untrack from cyclic GC if needed
free object memory
```对于容器,释放器必须减少对所包含对象的引用。

形状示例:```c
static void
Counter_dealloc(CounterObject *self)
{
    Py_TYPE(self)->tp_free((PyObject *)self);
}
```对于容器:```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);
}
```这是简化的。真实代码必须处理垃圾收集器跟踪和错误安全不变量。

## 8.22 垃圾收集器标头

参与循环垃圾回收的对象在可见之前可能有一个额外的GC`PyObject`标头。

从概念上讲:```text
GC header
PyObject header
type-specific payload
````PyObject *`指向对象头,而不是GC头。```text
memory block start
    GC metadata
    ob_refcnt       <--- PyObject * points here
    ob_type
    payload
```GC 头将对象链接到收集器结构中。

通常只有可以参与循环的类似容器的对象才需要这种跟踪。

拥有对其他 Python 对象的引用并可以参与循环的扩展类型必须正确实现 GC 协议。

## 8.23 调试构建

调试版本可能会在对象周围添加额外的字段或检查。

这可以包括:```text
reference count debugging
allocator padding
forbidden bytes around memory blocks
extra assertions
API misuse detection
```因此,扩展代码应避免假设超出记录的宏的精确原始内存布局。

不良风格:```c
obj->ob_refcnt++;
```更好的风格:```c
Py_INCREF(obj);
```不良风格:```c
obj->ob_type
```更好的风格:```c
Py_TYPE(obj)
```宏是扩展代码和 CPython 内部结构之间的兼容层。

## 8.24 不朽之物

现代 CPython 对于选定的长寿命对象有不朽对象的概念。不朽对象使用特殊的引用计数值并避免正常的引用计数破坏。

典型的候选对象包括基本的单例对象或运行时拥有的对象。

内部读者的实用要点:```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
``` Python 级别,引用计数检查已经是特定于实现的。在 C 级别,扩展作者应该使用官方参考管理 API

## 8.25 为什么`PyObject`事项`PyObject`是 CPython 运行时的通用货币。

解释器循环压入和弹出`PyObject *`价值观。

函数调用传递`PyObject *`论据。

集装箱商店`PyObject *`参考。

C 扩展 API 接收和返回`PyObject *`

类型槽操作于`PyObject *`

错误由 Python 异常对象表示。

模块是 Python 对象。

类是 Python 对象。

函数是 Python 对象。

代码对象是Python对象。

这种一致性使得 Python 充满活力。运行时可以通过一种表示操作任意值,同时通过类型对象分派行为。

## 8.26 有用的心理模型

当阅读 CPython C 代码时,首先假设这个形状:```text
PyObject *
    points to object header
        ob_refcnt
        ob_type
    followed by type-specific memory
```然后问:```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?
```这些问题可以防止在阅读 CPython 内部结构时出现大多数早期的困惑。

## 8.27 总结`PyObject`是 CPython 对象的基本标头。它为每个对象提供一个引用计数和一个类型指针。`PyVarObject`使用可变大小对象使用的大小字段扩展该标头。

固定大小对象的使用`PyObject_HEAD`

可变大小对象的使用`PyObject_VAR_HEAD`

对象头使 CPython 的动态对象系统成为可能。它允许通用运行时代码通过以下方式处理许多具体的对象布局`PyObject *`,而类型对象提供调用、算术、索引、属性、释放和协议调度所需的行为。